aboutsummaryrefslogtreecommitdiff
path: root/aoc2023/build/packages/gleam_otp
diff options
context:
space:
mode:
authorJ.J <thechairman@thechairman.info>2024-05-30 21:50:02 -0400
committerJ.J <thechairman@thechairman.info>2024-05-30 21:50:02 -0400
commit612fd986ab1e00b6d34dc1937136250e08e89325 (patch)
treea3c93952040c6afdf348b5831619a45db7ba0a2e /aoc2023/build/packages/gleam_otp
parent231c2b688d1e6cf0846d46e883da30e042a9c6cf (diff)
downloadgleam_aoc-612fd986ab1e00b6d34dc1937136250e08e89325.tar.gz
gleam_aoc-612fd986ab1e00b6d34dc1937136250e08e89325.zip
cleanup
Diffstat (limited to 'aoc2023/build/packages/gleam_otp')
-rw-r--r--aoc2023/build/packages/gleam_otp/LICENCE191
-rw-r--r--aoc2023/build/packages/gleam_otp/README.md91
-rw-r--r--aoc2023/build/packages/gleam_otp/gleam.toml19
-rw-r--r--aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Continue.hrl4
-rw-r--r--aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Ready.hrl1
-rw-r--r--aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Spec.hrl5
-rw-r--r--aoc2023/build/packages/gleam_otp/include/gleam@otp@intensity_tracker_IntensityTracker.hrl5
-rw-r--r--aoc2023/build/packages/gleam_otp/include/gleam@otp@supervisor_ChildSpec.hrl5
-rw-r--r--aoc2023/build/packages/gleam_otp/include/gleam@otp@supervisor_Spec.hrl6
-rw-r--r--aoc2023/build/packages/gleam_otp/include/gleam@otp@system_StatusInfo.hrl7
-rw-r--r--aoc2023/build/packages/gleam_otp/include/gleam@otp@task_Exit.hrl1
-rw-r--r--aoc2023/build/packages/gleam_otp/include/gleam@otp@task_Task.hrl6
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam/otp/actor.gleam504
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam/otp/intensity_tracker.gleam46
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam/otp/port.gleam9
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam/otp/supervisor.gleam410
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam/otp/system.gleam89
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam/otp/task.gleam151
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam@otp@actor.erl273
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam@otp@intensity_tracker.erl53
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam@otp@port.erl8
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam@otp@supervisor.erl322
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam@otp@system.erl43
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam@otp@task.erl111
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam_otp.app.src15
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam_otp.erl28
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam_otp.gleam27
-rw-r--r--aoc2023/build/packages/gleam_otp/src/gleam_otp_external.erl43
28 files changed, 2473 insertions, 0 deletions
diff --git a/aoc2023/build/packages/gleam_otp/LICENCE b/aoc2023/build/packages/gleam_otp/LICENCE
new file mode 100644
index 0000000..619ec77
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/LICENCE
@@ -0,0 +1,191 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2019, Louis Pilfold <louis@lpil.uk>.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/aoc2023/build/packages/gleam_otp/README.md b/aoc2023/build/packages/gleam_otp/README.md
new file mode 100644
index 0000000..3c313a1
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/README.md
@@ -0,0 +1,91 @@
+# Gleam OTP
+
+<a href="https://github.com/gleam-lang/otp/releases"><img src="https://img.shields.io/github/release/gleam-lang/otp" alt="GitHub release"></a>
+<a href="https://discord.gg/Fm8Pwmy"><img src="https://img.shields.io/discord/768594524158427167?color=blue" alt="Discord chat"></a>
+![CI](https://github.com/gleam-lang/otp/workflows/test/badge.svg?branch=main)
+
+A Gleam library for building fault tolerant multi-core programs using the
+actor model. It is compatible with Erlang's OTP framework.
+
+This library is experimental and will likely have many breaking changes in the
+future!
+
+Gleam’s actor system is built with a few primary goals:
+
+- Full type safety of actors and messages.
+- Be compatible with Erlang’s OTP actor framework.
+- Provide fault tolerance and self-healing through supervisors.
+- Have equivalent performance to Erlang’s OTP.
+
+This library documents its abstractions and functionality, but you may also wish
+to read the documentation or other material on Erlang’s OTP framework to get a
+fuller understanding of OTP, the problems it solves, and and the motivations for
+its design.
+
+## Usage
+
+Add this library to your Gleam project.
+
+```shell
+gleam add gleam_otp
+```
+
+## Actor hierarchy
+
+This library provides several different types of actor that can be used in
+Gleam programs.
+
+### Process
+
+The process is the lowest level building block of OTP, all other actors are
+built on top of processes either directly or indirectly. Typically this
+abstraction would be not be used very often in Gleam applications, favour
+other actor types that provide more functionality.
+
+Gleam's process module is defined in the `gleam_erlang` library.
+
+[[Documentation]](https://hexdocs.pm/gleam_erlang/gleam/erlang/process.html)
+
+### Actor
+
+The `actor` is the most commonly used process type in Gleam and serves as a good
+building block for other abstractions. Like Erlang's `gen_server` it handles
+OTP's system messages automatically to enable OTP's debugging and tracing
+functionality.
+
+[[Documentation]](https://hexdocs.pm/gleam_otp/gleam/otp/actor.html)
+
+### Task
+
+A task is a kind of process that performs a single task and then shuts down.
+Commonly tasks are used to convert sequential code into concurrent code by
+performing computation in another process.
+
+[[Documentation]](https://hexdocs.pm/gleam_otp/gleam/otp/task.html)
+
+### Supervisor
+
+Supervisors is a process that starts and then supervises a group of processes,
+restarting them if they crash. Supervisors can start other supervisors,
+resulting in a hierarchical process structure called a supervision tree,
+providing fault tolerance to a Gleam application.
+
+[[Documentation]](https://hexdocs.pm/gleam_otp/gleam/otp/supervisor.html)
+
+## Limitations and known issues
+
+This library is experimental there are some limitations that not yet been resolved.
+
+- There is no support for named processes. They are untyped global mutable
+ variables which may be uninitialized, more research is needed to find a
+ suitable type safe alternative.
+- There are relatively few actor abstractions provided by this library. More
+ will be added in the future.
+- Actors do not yet support all OTP system messages. Unsupported messages are
+ dropped.
+- Supervisors do not yet support different shutdown periods per child. In
+ practice this means that children that are supervisors do not get an
+ unlimited amount of time to shut down, as is expected in Erlang or Elixir.
+- This library has not seen much testing compared to the Erlang OTP
+ libraries, both in terms of unit tests and real world testing in
+ applications.
diff --git a/aoc2023/build/packages/gleam_otp/gleam.toml b/aoc2023/build/packages/gleam_otp/gleam.toml
new file mode 100644
index 0000000..26e451b
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/gleam.toml
@@ -0,0 +1,19 @@
+name = "gleam_otp"
+version = "0.8.0"
+licences = ["Apache-2.0"]
+description = "Fault tolerant multicore Gleam programs with OTP"
+
+gleam = ">= 0.32.0"
+
+repository = { type = "github", user = "gleam-lang", repo = "otp" }
+links = [
+ { title = "Website", href = "https://gleam.run" },
+ { title = "Sponsor", href = "https://github.com/sponsors/lpil" },
+]
+
+[dependencies]
+gleam_stdlib = "~> 0.32"
+gleam_erlang = "~> 0.22"
+
+[dev-dependencies]
+gleeunit = "~> 1.0"
diff --git a/aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Continue.hrl b/aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Continue.hrl
new file mode 100644
index 0000000..85677d1
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Continue.hrl
@@ -0,0 +1,4 @@
+-record(continue, {
+ state :: any(),
+ selector :: gleam@option:option(gleam@erlang@process:selector(any()))
+}).
diff --git a/aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Ready.hrl b/aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Ready.hrl
new file mode 100644
index 0000000..75faa95
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Ready.hrl
@@ -0,0 +1 @@
+-record(ready, {state :: any(), selector :: gleam@erlang@process:selector(any())}).
diff --git a/aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Spec.hrl b/aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Spec.hrl
new file mode 100644
index 0000000..5287439
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/include/gleam@otp@actor_Spec.hrl
@@ -0,0 +1,5 @@
+-record(spec, {
+ init :: fun(() -> gleam@otp@actor:init_result(any(), any())),
+ init_timeout :: integer(),
+ loop :: fun((any(), any()) -> gleam@otp@actor:next(any(), any()))
+}).
diff --git a/aoc2023/build/packages/gleam_otp/include/gleam@otp@intensity_tracker_IntensityTracker.hrl b/aoc2023/build/packages/gleam_otp/include/gleam@otp@intensity_tracker_IntensityTracker.hrl
new file mode 100644
index 0000000..3ed0b01
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/include/gleam@otp@intensity_tracker_IntensityTracker.hrl
@@ -0,0 +1,5 @@
+-record(intensity_tracker, {
+ limit :: integer(),
+ period :: integer(),
+ events :: list(integer())
+}).
diff --git a/aoc2023/build/packages/gleam_otp/include/gleam@otp@supervisor_ChildSpec.hrl b/aoc2023/build/packages/gleam_otp/include/gleam@otp@supervisor_ChildSpec.hrl
new file mode 100644
index 0000000..7afd07f
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/include/gleam@otp@supervisor_ChildSpec.hrl
@@ -0,0 +1,5 @@
+-record(child_spec, {
+ start :: fun((any()) -> {ok, gleam@erlang@process:subject(any())} |
+ {error, gleam@otp@actor:start_error()}),
+ returning :: fun((any(), gleam@erlang@process:subject(any())) -> any())
+}).
diff --git a/aoc2023/build/packages/gleam_otp/include/gleam@otp@supervisor_Spec.hrl b/aoc2023/build/packages/gleam_otp/include/gleam@otp@supervisor_Spec.hrl
new file mode 100644
index 0000000..b10bd9f
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/include/gleam@otp@supervisor_Spec.hrl
@@ -0,0 +1,6 @@
+-record(spec, {
+ argument :: any(),
+ max_frequency :: integer(),
+ frequency_period :: integer(),
+ init :: fun((gleam@otp@supervisor:children(any())) -> gleam@otp@supervisor:children(any()))
+}).
diff --git a/aoc2023/build/packages/gleam_otp/include/gleam@otp@system_StatusInfo.hrl b/aoc2023/build/packages/gleam_otp/include/gleam@otp@system_StatusInfo.hrl
new file mode 100644
index 0000000..99ab4cb
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/include/gleam@otp@system_StatusInfo.hrl
@@ -0,0 +1,7 @@
+-record(status_info, {
+ module :: gleam@erlang@atom:atom_(),
+ parent :: gleam@erlang@process:pid_(),
+ mode :: gleam@otp@system:mode(),
+ debug_state :: gleam@otp@system:debug_state(),
+ state :: gleam@dynamic:dynamic_()
+}).
diff --git a/aoc2023/build/packages/gleam_otp/include/gleam@otp@task_Exit.hrl b/aoc2023/build/packages/gleam_otp/include/gleam@otp@task_Exit.hrl
new file mode 100644
index 0000000..7c83874
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/include/gleam@otp@task_Exit.hrl
@@ -0,0 +1 @@
+-record(exit, {reason :: gleam@dynamic:dynamic_()}).
diff --git a/aoc2023/build/packages/gleam_otp/include/gleam@otp@task_Task.hrl b/aoc2023/build/packages/gleam_otp/include/gleam@otp@task_Task.hrl
new file mode 100644
index 0000000..959bea8
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/include/gleam@otp@task_Task.hrl
@@ -0,0 +1,6 @@
+-record(task, {
+ owner :: gleam@erlang@process:pid_(),
+ pid :: gleam@erlang@process:pid_(),
+ monitor :: gleam@erlang@process:process_monitor(),
+ selector :: gleam@erlang@process:selector(gleam@otp@task:message(any()))
+}).
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/actor.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/actor.gleam
new file mode 100644
index 0000000..9f6a6c4
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam/otp/actor.gleam
@@ -0,0 +1,504 @@
+//// This module provides the _Actor_ abstraction, one of the most common
+//// building blocks of Gleam OTP programs.
+////
+//// An Actor is a process like any other BEAM process and can be be used to hold
+//// state, execute code, and communicate with other processes by sending and
+//// receiving messages. The advantage of using the actor abstraction over a bare
+//// process is that it provides a single interface for commonly needed
+//// functionality, including support for the [tracing and debugging
+//// features in OTP](erlang-sys).
+////
+//// Gleam's Actor is similar to Erlang's `gen_server` and Elixir's `GenServer`
+//// but differs in that it offers a fully typed interface. This different API is
+//// why Gleam uses the name Actor rather than some variation of generic-server.
+////
+//// [erlang-sys]: https://www.erlang.org/doc/man/sys.html
+////
+//// ## Example
+////
+//// An Actor can be used to create a client-server interaction between an Actor
+//// (the server) and other processes (the clients). In this example we have an
+//// Actor that works as a stack, allowing clients to push and pop elements.
+////
+//// ```gleam
+//// pub fn main() {
+//// // Start the actor with initial state of an empty list, and the
+//// // `handle_message` callback function (defined below).
+//// // We assert that it starts successfully.
+//// //
+//// // In real-world Gleam OTP programs we would likely write a wrapper functions
+//// // called `start`, `push` `pop`, `shutdown` to start and interact with the
+//// // Actor. We are not doing that here for the sake of showing how the Actor
+//// // API works.
+//// let assert Ok(actor) = actor.start([], handle_message)
+////
+//// // We can send a message to the actor to push elements onto the stack.
+//// process.send(actor, Push("Joe"))
+//// process.send(actor, Push("Mike"))
+//// process.send(actor, Push("Robert"))
+////
+//// // The `Push` message expects no response, these messages are sent purely for
+//// // the side effect of mutating the state held by the actor.
+//// //
+//// // We can also send the `Pop` message to take a value off of the actor's
+//// // stack. This message expects a response, so we use `process.call` to send a
+//// // message and wait until a reply is received.
+//// //
+//// // In this instance we are giving the actor 10 milliseconds to reply, if the
+//// // `call` function doesn't get a reply within this time it will panic and
+//// // crash the client process.
+//// let assert Ok("Robert") = process.call(actor, Pop, 10)
+//// let assert Ok("Mike") = process.call(actor, Pop, 10)
+//// let assert Ok("Joe") = process.call(actor, Pop, 10)
+////
+//// // The stack is now empty, so if we pop again the actor replies with an error.
+//// let assert Error(Nil) = process.call(actor, Pop, 10)
+////
+//// // Lastly, we can send a message to the actor asking it to shut down.
+//// process.send(actor, Shutdown)
+//// }
+//// ```
+////
+//// Here is the code that is used to implement this actor:
+////
+//// ```gleam
+//// // First step of implementing the stack Actor is to define the message type that
+//// // it can receive.
+//// //
+//// // The type of the elements in the stack is no fixed so a type parameter is used
+//// // for it instead of a concrete type such as `String` or `Int`.
+//// pub type Message(element) {
+//// // The `Shutdown` message is used to tell the actor to stop.
+//// // It is the simplest message type, it contains no data.
+//// Shutdown
+////
+//// // The `Push` message is used to add a new element to the stack.
+//// // It contains the item to add, the type of which is the `element`
+//// // parameterised type.
+//// Push(push: element)
+////
+//// // The `Pop` message is used to remove an element from the stack.
+//// // It contains a `Subject`, which is used to send the response back to the
+//// // message sender. In this case the reply is of type `Result(element, Nil)`.
+//// Pop(reply_with: Subject(Result(element, Nil)))
+//// }
+////
+//// // The last part is to implement the `handle_message` callback function.
+//// //
+//// // This function is called by the Actor each for each message it receives.
+//// // Actor is single threaded only does one thing at a time, so it handles
+//// // messages sequentially and one at a time, in the order they are received.
+//// //
+//// // The function takes the message and the current state, and returns a data
+//// // structure that indicates what to do next, along with the new state.
+//// fn handle_message(
+//// message: Message(e),
+//// stack: List(e),
+//// ) -> actor.Next(Message(e), List(e)) {
+//// case message {
+//// // For the `Shutdown` message we return the `actor.Stop` value, which causes
+//// // the actor to discard any remaining messages and stop.
+//// Shutdown -> actor.Stop(process.Normal)
+////
+//// // For the `Push` message we add the new element to the stack and return
+//// // `actor.continue` with this new stack, causing the actor to process any
+//// // queued messages or wait for more.
+//// Push(value) -> {
+//// let new_state = [value, ..stack]
+//// actor.continue(new_state)
+//// }
+////
+//// // For the `Pop` message we attempt to remove an element from the stack,
+//// // sending it or an error back to the caller, before continuing.
+//// Pop(client) ->
+//// case stack {
+//// [] -> {
+//// // When the stack is empty we can't pop an element, so we send an
+//// // error back.
+//// process.send(client, Error(Nil))
+//// actor.continue([])
+//// }
+////
+//// [first, ..rest] -> {
+//// // Otherwise we send the first element back and use the remaining
+//// // elements as the new state.
+//// process.send(client, Ok(first))
+//// actor.continue(rest)
+//// }
+//// }
+//// }
+//// }
+//// ```
+
+import gleam/erlang/process.{
+ type ExitReason, type Pid, type Selector, type Subject, Abnormal,
+}
+import gleam/erlang/charlist.{type Charlist}
+import gleam/otp/system.{
+ type DebugState, type Mode, type StatusInfo, type SystemMessage, GetState,
+ GetStatus, Resume, Running, StatusInfo, Suspend, Suspended,
+}
+import gleam/string
+import gleam/dynamic.{type Dynamic}
+import gleam/erlang/atom
+import gleam/option.{type Option, None, Some}
+
+type Message(message) {
+ /// A regular message excepted by the process
+ Message(message)
+
+ /// An OTP system message, for debugging or maintenance
+ System(SystemMessage)
+
+ /// An unexpected message
+ Unexpected(Dynamic)
+}
+
+/// The type used to indicate what to do after handling a message.
+///
+pub type Next(message, state) {
+ /// Continue handling messages.
+ ///
+ Continue(state: state, selector: Option(Selector(message)))
+
+ /// Stop handling messages and shut down.
+ ///
+ Stop(ExitReason)
+}
+
+pub fn continue(state: state) -> Next(message, state) {
+ Continue(state, None)
+}
+
+pub fn with_selector(
+ value: Next(message, state),
+ selector: Selector(message),
+) -> Next(message, state) {
+ case value {
+ Continue(state, _) -> Continue(state, Some(selector))
+ _ -> value
+ }
+}
+
+/// The type used to indicate whether an actor has started successfully or not.
+///
+pub type InitResult(state, message) {
+ /// The actor has successfully initialised. The actor can start handling
+ /// messages and actor's channel sender can be returned to the parent
+ /// process.
+ ///
+ Ready(state: state, selector: Selector(message))
+
+ /// The actor has failed to initialise. The actor shuts down and an error is
+ /// returned to the parent process.
+ ///
+ Failed(String)
+}
+
+type Self(state, msg) {
+ Self(
+ mode: Mode,
+ parent: Pid,
+ state: state,
+ subject: Subject(msg),
+ selector: Selector(Message(msg)),
+ debug_state: DebugState,
+ message_handler: fn(msg, state) -> Next(msg, state),
+ )
+}
+
+/// This data structure holds all the values required by the `start_spec`
+/// function in order to create an actor.
+///
+/// If you do not need to configure the initialisation behaviour of your actor
+/// consider using the `start` function.
+///
+pub type Spec(state, msg) {
+ Spec(
+ /// The initialisation functionality for the actor. This function is called
+ /// just after the actor starts but before the channel sender is returned
+ /// to the parent.
+ ///
+ /// This function is used to ensure that any required data or state is
+ /// correct. If this function returns an error it means that the actor has
+ /// failed to start and an error is returned to the parent.
+ ///
+ init: fn() -> InitResult(state, msg),
+ /// How many milliseconds the `init` function has to return before it is
+ /// considered to have taken too long and failed.
+ ///
+ init_timeout: Int,
+ /// This function is called to handle each message that the actor receives.
+ ///
+ loop: fn(msg, state) -> Next(msg, state),
+ )
+}
+
+// TODO: Check needed functionality here to be OTP compatible
+fn exit_process(reason: ExitReason) -> ExitReason {
+ // TODO
+ reason
+}
+
+fn receive_message(self: Self(state, msg)) -> Message(msg) {
+ let selector = case self.mode {
+ // When suspended we only respond to system messages
+ Suspended ->
+ process.new_selector()
+ |> selecting_system_messages
+
+ // When running we respond to all messages
+ Running ->
+ // We add the handler for unexpected messages first so that the user
+ // supplied selector can override it if desired
+ process.new_selector()
+ |> process.selecting_anything(Unexpected)
+ |> process.merge_selector(self.selector)
+ |> selecting_system_messages
+ }
+
+ process.select_forever(selector)
+}
+
+fn selecting_system_messages(
+ selector: Selector(Message(msg)),
+) -> Selector(Message(msg)) {
+ selector
+ |> process.selecting_record3(
+ atom.create_from_string("system"),
+ convert_system_message,
+ )
+}
+
+@external(erlang, "gleam_otp_external", "convert_system_message")
+fn convert_system_message(a: Dynamic, b: Dynamic) -> Message(msg)
+
+fn process_status_info(self: Self(state, msg)) -> StatusInfo {
+ StatusInfo(
+ module: atom.create_from_string("gleam@otp@actor"),
+ parent: self.parent,
+ mode: self.mode,
+ debug_state: self.debug_state,
+ state: dynamic.from(self.state),
+ )
+}
+
+fn loop(self: Self(state, msg)) -> ExitReason {
+ case receive_message(self) {
+ System(system) ->
+ case system {
+ GetState(callback) -> {
+ callback(dynamic.from(self.state))
+ loop(self)
+ }
+ Resume(callback) -> {
+ callback()
+ loop(Self(..self, mode: Running))
+ }
+ Suspend(callback) -> {
+ callback()
+ loop(Self(..self, mode: Suspended))
+ }
+ GetStatus(callback) -> {
+ callback(process_status_info(self))
+ loop(self)
+ }
+ }
+
+ Unexpected(message) -> {
+ log_warning(
+ charlist.from_string("Actor discarding unexpected message: ~s"),
+ [charlist.from_string(string.inspect(message))],
+ )
+ loop(self)
+ }
+
+ Message(msg) ->
+ case self.message_handler(msg, self.state) {
+ Stop(reason) -> exit_process(reason)
+ Continue(state: state, selector: new_selector) -> {
+ let selector =
+ new_selector
+ |> option.map(init_selector(self.subject, _))
+ |> option.unwrap(self.selector)
+ loop(Self(..self, state: state, selector: selector))
+ }
+ }
+ }
+}
+
+// TODO: replace this when we have Gleam bindings to the logger
+@external(erlang, "logger", "warning")
+fn log_warning(a: Charlist, b: List(Charlist)) -> Nil
+
+fn initialise_actor(
+ spec: Spec(state, msg),
+ ack: Subject(Result(Subject(msg), ExitReason)),
+) {
+ let subject = process.new_subject()
+ case spec.init() {
+ Ready(state, selector) -> {
+ let selector = init_selector(subject, selector)
+ // Signal to parent that the process has initialised successfully
+ process.send(ack, Ok(subject))
+ // Start message receive loop
+ let self =
+ Self(
+ state: state,
+ parent: process.subject_owner(ack),
+ subject: subject,
+ selector: selector,
+ message_handler: spec.loop,
+ debug_state: system.debug_state([]),
+ mode: Running,
+ )
+ loop(self)
+ }
+
+ Failed(reason) -> {
+ process.send(ack, Error(Abnormal(reason)))
+ exit_process(Abnormal(reason))
+ }
+ }
+}
+
+fn init_selector(subject, selector) {
+ process.new_selector()
+ |> process.selecting(subject, Message)
+ |> process.merge_selector(process.map_selector(selector, Message))
+}
+
+pub type StartError {
+ InitTimeout
+ InitFailed(ExitReason)
+ InitCrashed(Dynamic)
+}
+
+/// The result of starting a Gleam actor.
+///
+/// This type is compatible with Gleam supervisors. If you wish to convert it
+/// to a type compatible with Erlang supervisors see the `ErlangStartResult`
+/// type and `erlang_start_result` function.
+///
+pub type StartResult(msg) =
+ Result(Subject(msg), StartError)
+
+/// An Erlang supervisor compatible process start result.
+///
+/// If you wish to convert this into a `StartResult` compatible with Gleam
+/// supervisors see the `from_erlang_start_result` and `wrap_erlang_starter`
+/// functions.
+///
+pub type ErlangStartResult =
+ Result(Pid, Dynamic)
+
+/// Convert a Gleam actor start result into an Erlang supervisor compatible
+/// process start result.
+///
+pub fn to_erlang_start_result(res: StartResult(msg)) -> ErlangStartResult {
+ case res {
+ Ok(x) -> Ok(process.subject_owner(x))
+ Error(x) -> Error(dynamic.from(x))
+ }
+}
+
+type StartInitMessage(msg) {
+ Ack(Result(Subject(msg), ExitReason))
+ Mon(process.ProcessDown)
+}
+
+// TODO: test init_timeout. Currently if we test it eunit prints an error from
+// the process death. How do we avoid this?
+//
+/// Start an actor from a given specification. If the actor's `init` function
+/// returns an error or does not return within `init_timeout` then an error is
+/// returned.
+///
+/// If you do not need to specify the initialisation behaviour of your actor
+/// consider using the `start` function.
+///
+pub fn start_spec(spec: Spec(state, msg)) -> Result(Subject(msg), StartError) {
+ let ack_subject = process.new_subject()
+
+ let child =
+ process.start(
+ linked: True,
+ running: fn() { initialise_actor(spec, ack_subject) },
+ )
+
+ let monitor = process.monitor_process(child)
+ let selector =
+ process.new_selector()
+ |> process.selecting(ack_subject, Ack)
+ |> process.selecting_process_down(monitor, Mon)
+
+ let result = case process.select(selector, spec.init_timeout) {
+ // Child started OK
+ Ok(Ack(Ok(channel))) -> Ok(channel)
+
+ // Child initialiser returned an error
+ Ok(Ack(Error(reason))) -> Error(InitFailed(reason))
+
+ // Child went down while initialising
+ Ok(Mon(down)) -> Error(InitCrashed(down.reason))
+
+ // Child did not finish initialising in time
+ Error(Nil) -> {
+ process.kill(child)
+ Error(InitTimeout)
+ }
+ }
+
+ // Remove the monitor used for the starting of the actor as to avoid an extra
+ // message arriving at the parent if the child dies later.
+ process.demonitor_process(monitor)
+
+ result
+}
+
+/// Start an actor with a given initial state and message handling loop
+/// function.
+///
+/// This function returns a `Result` but it will always be `Ok` so it is safe
+/// to use with `assert` if you are not starting this actor as part of a
+/// supervision tree.
+///
+/// If you wish to configure the initialisation behaviour of a new actor see
+/// the `Spec` record and the `start_spec` function.
+///
+pub fn start(
+ state: state,
+ loop: fn(msg, state) -> Next(msg, state),
+) -> Result(Subject(msg), StartError) {
+ start_spec(Spec(
+ init: fn() { Ready(state, process.new_selector()) },
+ loop: loop,
+ init_timeout: 5000,
+ ))
+}
+
+/// Send a message over a given channel.
+///
+/// This is a re-export of `process.send`, for the sake of convenience.
+///
+pub fn send(subject: Subject(msg), msg: msg) -> Nil {
+ process.send(subject, msg)
+}
+
+// TODO: test
+/// Send a synchronous message and wait for a response from the receiving
+/// process.
+///
+/// If a reply is not received within the given timeout then the sender process
+/// crashes. If you wish receive a `Result` rather than crashing see the
+/// `process.try_call` function.
+///
+/// This is a re-export of `process.call`, for the sake of convenience.
+///
+pub fn call(
+ selector: Subject(message),
+ make_message: fn(Subject(reply)) -> message,
+ timeout: Int,
+) -> reply {
+ process.call(selector, make_message, timeout)
+}
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/intensity_tracker.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/intensity_tracker.gleam
new file mode 100644
index 0000000..2044be0
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam/otp/intensity_tracker.gleam
@@ -0,0 +1,46 @@
+//// The intensity tracker is used to monitor how frequently an event happens,
+//// erroring if it happens too many times within a period of time.
+
+import gleam/list
+
+// TODO: test
+pub opaque type IntensityTracker {
+ IntensityTracker(limit: Int, period: Int, events: List(Int))
+}
+
+pub type TooIntense {
+ TooIntense
+}
+
+pub fn new(limit limit: Int, period period: Int) -> IntensityTracker {
+ IntensityTracker(limit: limit, period: period, events: [])
+}
+
+@external(erlang, "erlang", "monotonic_time")
+fn monotonic_time(a: Int) -> Int
+
+fn now_seconds() -> Int {
+ monotonic_time(1)
+}
+
+pub fn trim_window(events: List(Int), now: Int, period: Int) -> List(Int) {
+ case events {
+ [] -> []
+ [event, ..events] ->
+ case now >= event + period {
+ True -> [event, ..trim_window(events, now, period)]
+ False -> []
+ }
+ }
+}
+
+pub fn add_event(
+ tracker: IntensityTracker,
+) -> Result(IntensityTracker, TooIntense) {
+ let now = now_seconds()
+ let events = trim_window([now, ..tracker.events], now, tracker.period)
+ case list.length(events) >= tracker.limit {
+ True -> Error(TooIntense)
+ False -> Ok(IntensityTracker(..tracker, events: events))
+ }
+}
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/port.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/port.gleam
new file mode 100644
index 0000000..4e1b4d8
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam/otp/port.gleam
@@ -0,0 +1,9 @@
+/// Ports are how code running on the Erlang virtual machine interacts with
+/// the outside world. Bytes of data can be sent to and read from ports,
+/// providing a form of message passing to an external program or resource.
+///
+/// For more information on ports see the [Erlang ports documentation][1].
+///
+/// [1]: https://erlang.org/doc/reference_manual/ports.html
+///
+pub type Port
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/supervisor.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/supervisor.gleam
new file mode 100644
index 0000000..b99ad8e
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam/otp/supervisor.gleam
@@ -0,0 +1,410 @@
+// TODO: specify amount of time permitted for shut-down
+import gleam/result
+import gleam/string
+import gleam/option.{type Option, None, Some}
+import gleam/erlang/process.{type Pid, type Subject}
+import gleam/otp/actor.{type StartError}
+import gleam/otp/intensity_tracker.{type IntensityTracker}
+import gleam/erlang/node.{type Node}
+
+/// This data structure holds all the values required by the `start_spec`
+/// function in order to create an supervisor.
+///
+/// If you do not need to configure the behaviour of your supervisor consider
+/// using the `start` function.
+///
+pub type Spec(argument, return) {
+ Spec(
+ argument: argument,
+ max_frequency: Int,
+ frequency_period: Int,
+ init: fn(Children(argument)) -> Children(return),
+ )
+}
+
+/// This type represents the starting children of a supervisor within the
+/// `init` function.
+///
+pub opaque type Children(argument) {
+ Ready(Starter(argument))
+ Failed(ChildStartError)
+}
+
+/// This type contains all the information required to start a new child and
+/// add it to the `Children`.
+///
+/// This is typically created with the `worker` function.
+///
+pub opaque type ChildSpec(msg, argument, returning) {
+ ChildSpec(
+ // TODO: merge this into one field
+ start: fn(argument) -> Result(Subject(msg), StartError),
+ returning: fn(argument, Subject(msg)) -> returning,
+ )
+}
+
+type ChildStartError {
+ ChildStartError(previous_pid: Option(Pid), error: StartError)
+}
+
+pub opaque type Message {
+ Exit(process.ExitMessage)
+ RetryRestart(Pid)
+}
+
+type Instruction {
+ StartAll
+ StartFrom(Pid)
+}
+
+type State(a) {
+ State(
+ restarts: IntensityTracker,
+ starter: Starter(a),
+ retry_restarts: Subject(Pid),
+ )
+}
+
+type Starter(argument) {
+ Starter(
+ argument: argument,
+ exec: Option(
+ fn(Instruction) ->
+ Result(#(Starter(argument), Instruction), ChildStartError),
+ ),
+ )
+}
+
+type Child(argument) {
+ Child(pid: Pid, argument: argument)
+}
+
+fn start_child(
+ child_spec: ChildSpec(msg, argument_in, argument_out),
+ argument: argument_in,
+) -> Result(Child(argument_out), ChildStartError) {
+ use subject <- result.then(
+ child_spec.start(argument)
+ |> result.map_error(ChildStartError(None, _)),
+ )
+
+ Ok(Child(
+ pid: process.subject_owner(subject),
+ // Merge the new child's pid into the argument to produce the new argument
+ // used to start any remaining children.
+ argument: child_spec.returning(argument, subject),
+ ))
+}
+
+// TODO: more sophsiticated stopping of processes. i.e. give supervisors
+// more time to shut down.
+fn shutdown_child(pid: Pid, _spec: ChildSpec(msg, arg_1, arg_2)) -> Nil {
+ process.send_exit(pid)
+}
+
+fn perform_instruction_for_child(
+ argument: argument_in,
+ instruction: Instruction,
+ child_spec: ChildSpec(msg, argument_in, argument_out),
+ child: Child(argument_out),
+) -> Result(#(Child(argument_out), Instruction), ChildStartError) {
+ let current = child.pid
+ case instruction {
+ // This child is older than the StartFrom target, we don't need to
+ // restart it
+ StartFrom(target) if target != current -> Ok(#(child, instruction))
+
+ // This pid either is the cause of the problem, or we have the StartAll
+ // instruction. Either way it and its younger siblings need to be restarted.
+ _ -> {
+ shutdown_child(current, child_spec)
+ use child <- result.then(start_child(child_spec, argument))
+ Ok(#(child, StartAll))
+ }
+ }
+}
+
+fn add_child_to_starter(
+ starter: Starter(argument_in),
+ child_spec: ChildSpec(msg, argument_in, argument_out),
+ child: Child(argument_out),
+) -> Starter(argument_out) {
+ let starter = fn(instruction) {
+ // Restart the older children. We use `try` to return early if the older
+ // children failed to start
+ use #(starter, instruction) <- result.then(case starter.exec {
+ Some(start) -> start(instruction)
+ None -> Ok(#(starter, instruction))
+ })
+
+ // Perform the instruction, restarting the child as required
+ use #(child, instruction) <- result.then(perform_instruction_for_child(
+ starter.argument,
+ instruction,
+ child_spec,
+ child,
+ ))
+
+ // Create a new starter for the next time the supervisor needs to restart
+ let starter = add_child_to_starter(starter, child_spec, child)
+
+ Ok(#(starter, instruction))
+ }
+
+ Starter(exec: Some(starter), argument: child.argument)
+}
+
+fn start_and_add_child(
+ state: Starter(argument_0),
+ child_spec: ChildSpec(msg, argument_0, argument_1),
+) -> Children(argument_1) {
+ case start_child(child_spec, state.argument) {
+ Ok(child) -> Ready(add_child_to_starter(state, child_spec, child))
+ Error(reason) -> Failed(reason)
+ }
+}
+
+/// Add a child to the collection of children of the supervisor
+///
+/// This function starts the child from the child spec.
+///
+pub fn add(
+ children: Children(argument),
+ child_spec: ChildSpec(msg, argument, new_argument),
+) -> Children(new_argument) {
+ case children {
+ // If one of the previous children has failed then we cannot continue
+ Failed(fail) -> Failed(fail)
+
+ // If everything is OK so far then we can add the child
+ Ready(state) -> start_and_add_child(state, child_spec)
+ }
+}
+
+// TODO: test
+// TODO: unlimitd shut down duration
+/// Prepare a new supervisor type child.
+///
+/// If you wish to prepare a new non-supervisor type child see the `worker`
+/// function.
+///
+/// If you wish to change the type of the argument for later children see the
+/// `returning` function.
+///
+/// Note: Gleam supervisors do not yet support different shutdown periods per
+/// child so this function is currently identical in behaviour to `worker`. It is
+/// recommended to use this function for supervisor children nevertheless so the
+/// correct shut down behaviour is used in later releases of this library.
+///
+pub fn supervisor(
+ start: fn(argument) -> Result(Subject(msg), StartError),
+) -> ChildSpec(msg, argument, argument) {
+ ChildSpec(start: start, returning: fn(argument, _channel) { argument })
+}
+
+/// Prepare a new worker type child.
+///
+/// If you wish to prepare a new supervisor type child see the `supervisor`
+/// function.
+///
+/// If you wish to change the type of the argument for later children see the
+/// `returning` function.
+///
+pub fn worker(
+ start: fn(argument) -> Result(Subject(msg), StartError),
+) -> ChildSpec(msg, argument, argument) {
+ ChildSpec(start: start, returning: fn(argument, _channel) { argument })
+}
+
+// TODO: test
+/// As each child is added to a supervisors children a new argument is prepared
+/// with which to start the next child. By default argument is the same as the
+/// previous argument, but this function can be used to change it to something
+/// else by passing a function that takes the previous argument and the sender
+/// of the previous child.
+///
+pub fn returning(
+ child: ChildSpec(msg, argument_a, argument_b),
+ updater: fn(argument_a, Subject(msg)) -> argument_c,
+) -> ChildSpec(msg, argument_a, argument_c) {
+ ChildSpec(start: child.start, returning: updater)
+}
+
+fn init(
+ spec: Spec(argument, return),
+) -> actor.InitResult(State(return), Message) {
+ // Create a subject so that we can asynchronously retry restarting when we
+ // fail to bring an exited child
+ let retry = process.new_subject()
+
+ // Trap exits so that we get a message when a child crashes
+ process.trap_exits(True)
+
+ // Combine selectors
+ let selector =
+ process.new_selector()
+ |> process.selecting(retry, RetryRestart)
+ |> process.selecting_trapped_exits(Exit)
+
+ // Start any children
+ let result =
+ Starter(argument: spec.argument, exec: None)
+ |> Ready
+ |> spec.init
+
+ // Pass back up the result
+ case result {
+ Ready(starter) -> {
+ let restarts =
+ intensity_tracker.new(
+ limit: spec.max_frequency,
+ period: spec.frequency_period,
+ )
+ let state =
+ State(starter: starter, restarts: restarts, retry_restarts: retry)
+ actor.Ready(state, selector)
+ }
+
+ Failed(error) ->
+ actor.Failed(case error.error {
+ actor.InitTimeout -> "Child initialisation timed out"
+ actor.InitCrashed(reason) ->
+ string.append(
+ "Child crashed during initialisation: ",
+ string.inspect(reason),
+ )
+ actor.InitFailed(reason) ->
+ string.append(
+ "Child failed to start during initialisation: ",
+ string.inspect(reason),
+ )
+ })
+ }
+}
+
+type HandleExitError {
+ RestartFailed(pid: Pid, restarts: IntensityTracker)
+ TooManyRestarts
+}
+
+fn handle_exit(pid: Pid, state: State(a)) -> actor.Next(Message, State(a)) {
+ let outcome = {
+ // If we are handling an exit then we must have some children
+ let assert Some(start) = state.starter.exec
+
+ // Check to see if there has been too many restarts in this period
+ use restarts <- result.then(
+ state.restarts
+ |> intensity_tracker.add_event
+ |> result.map_error(fn(_) { TooManyRestarts }),
+ )
+
+ // Restart the exited child and any following children
+ use #(starter, _) <- result.then(
+ start(StartFrom(pid))
+ |> result.map_error(fn(e: ChildStartError) {
+ RestartFailed(option.unwrap(e.previous_pid, pid), restarts)
+ }),
+ )
+
+ Ok(State(..state, starter: starter, restarts: restarts))
+ }
+
+ case outcome {
+ Ok(state) -> actor.continue(state)
+ Error(RestartFailed(failed_child, restarts)) -> {
+ // Asynchronously enqueue the restarting of this child again as we were
+ // unable to restart them this time. We do this asynchronously as we want
+ // to have a chance to handle any system messages that have come in.
+ process.send(state.retry_restarts, failed_child)
+ let state = State(..state, restarts: restarts)
+ actor.continue(state)
+ }
+ Error(TooManyRestarts) ->
+ actor.Stop(process.Abnormal(
+ "Child processes restarted too many times within allowed period",
+ ))
+ }
+}
+
+fn loop(
+ message: Message,
+ state: State(argument),
+) -> actor.Next(Message, State(argument)) {
+ case message {
+ Exit(exit_message) -> handle_exit(exit_message.pid, state)
+ RetryRestart(pid) -> handle_exit(pid, state)
+ }
+}
+
+/// Start a supervisor from a given specification.
+///
+pub fn start_spec(spec: Spec(a, b)) -> Result(Subject(Message), StartError) {
+ actor.start_spec(actor.Spec(
+ init: fn() { init(spec) },
+ loop: loop,
+ init_timeout: 60_000,
+ ))
+}
+
+/// Start a supervisor from a given `init` function.
+///
+/// The init argument passed to children will be `Nil` and the maximum restart
+/// intensity will be 1 restart per 5 seconds (the same as the default for
+/// [Erlang supervisors][erl-sup]). If you wish to specify these values, see
+/// the `start_spec` function and the `Spec` type.
+///
+/// [erl-sup]: https://www.erlang.org/doc/design_principles/sup_princ.html#maximum-restart-intensity
+///
+pub fn start(
+ init: fn(Children(Nil)) -> Children(a),
+) -> Result(Subject(Message), StartError) {
+ start_spec(Spec(
+ init: init,
+ argument: Nil,
+ max_frequency: 1,
+ frequency_period: 5,
+ ))
+}
+
+/// A type used to describe the situation in which an Erlang based application
+/// is starting.
+///
+/// For more information see the [Erlang distributed application
+/// documentation][1] and the Learn Your Some Erlang chapter on [distributed
+/// applications][2].
+///
+/// [1]: https://erlang.org/doc/design_principles/distributed_applications.html
+/// [2]: https://learnyousomeerlang.com/distributed-otp-applications
+///
+pub type ApplicationStartMode {
+ Normal
+ Takeover(Node)
+ Failover(Node)
+}
+
+pub type ApplicationStop
+
+@external(erlang, "gleam_otp_external", "application_stopped")
+pub fn application_stopped() -> ApplicationStop
+
+/// The result of starting a Gleam actor.
+///
+/// This type is compatible with Gleam supervisors. If you wish to convert it
+/// to a type compatible with Erlang supervisors see the `ErlangStartResult`
+/// type and `erlang_start_result` function.
+///
+pub type StartResult(msg) =
+ actor.StartResult(msg)
+
+/// An Erlang supervisor compatible process start result.
+///
+pub type ErlangStartResult =
+ actor.ErlangStartResult
+
+/// Convert a Gleam actor start result into an Erlang supervisor compatible
+/// process start result.
+///
+pub fn to_erlang_start_result(res: StartResult(msg)) -> ErlangStartResult {
+ actor.to_erlang_start_result(res)
+}
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/system.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/system.gleam
new file mode 100644
index 0000000..c05646b
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam/otp/system.gleam
@@ -0,0 +1,89 @@
+import gleam/dynamic.{type Dynamic}
+import gleam/erlang/atom.{type Atom}
+import gleam/erlang/process.{type Pid}
+
+pub type Mode {
+ Running
+ Suspended
+}
+
+pub type DebugOption {
+ NoDebug
+}
+
+pub type DebugState
+
+@external(erlang, "sys", "debug_options")
+pub fn debug_state(a: List(DebugOption)) -> DebugState
+
+pub type StatusInfo {
+ StatusInfo(
+ module: Atom,
+ parent: Pid,
+ mode: Mode,
+ debug_state: DebugState,
+ state: Dynamic,
+ )
+}
+
+// TODO: document
+// TODO: implement remaining messages
+pub type SystemMessage {
+ // {replace_state, StateFn}
+ // {change_code, Mod, Vsn, Extra}
+ // {terminate, Reason}
+ // {debug, {log, Flag}}
+ // {debug, {trace, Flag}}
+ // {debug, {log_to_file, FileName}}
+ // {debug, {statistics, Flag}}
+ // {debug, no_debug}
+ // {debug, {install, {Func, FuncState}}}
+ // {debug, {install, {FuncId, Func, FuncState}}}
+ // {debug, {remove, FuncOrId}}
+ Resume(fn() -> Nil)
+ Suspend(fn() -> Nil)
+ GetState(fn(Dynamic) -> Nil)
+ GetStatus(fn(StatusInfo) -> Nil)
+}
+
+type DoNotLeak
+
+/// Get the state of a given OTP compatible process. This function is only
+/// intended for debugging.
+///
+/// For more information see the [Erlang documentation][1].
+///
+/// [1]: https://erlang.org/doc/man/sys.html#get_state-1
+///
+@external(erlang, "sys", "get_state")
+pub fn get_state(from from: Pid) -> Dynamic
+
+@external(erlang, "sys", "suspend")
+fn erl_suspend(a: Pid) -> DoNotLeak
+
+/// Request an OTP compatible process to suspend, causing it to only handle
+/// system messages.
+///
+/// For more information see the [Erlang documentation][1].
+///
+/// [1]: https://erlang.org/doc/man/sys.html#suspend-1
+///
+pub fn suspend(pid: Pid) -> Nil {
+ erl_suspend(pid)
+ Nil
+}
+
+@external(erlang, "sys", "resume")
+fn erl_resume(from from: Pid) -> DoNotLeak
+
+/// Request a suspended OTP compatible process to result, causing it to handle
+/// all messages rather than only system messages.
+///
+/// For more information see the [Erlang documentation][1].
+///
+/// [1]: https://erlang.org/doc/man/sys.html#resume-1
+///
+pub fn resume(pid: Pid) -> Nil {
+ erl_resume(pid)
+ Nil
+}
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam/otp/task.gleam b/aoc2023/build/packages/gleam_otp/src/gleam/otp/task.gleam
new file mode 100644
index 0000000..b2b2c5c
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam/otp/task.gleam
@@ -0,0 +1,151 @@
+//// A task is a kind of process that performs a single task and then shuts
+//// down. Commonly tasks are used to convert sequential code into concurrent
+//// code by performing computation in another process.
+////
+//// let task = task.async(fn() { do_some_work() })
+//// let value = do_some_other_work()
+//// value + task.await(task, 100)
+////
+//// Tasks spawned with async can be awaited on by their caller process (and
+//// only their caller) as shown in the example above. They are implemented by
+//// spawning a process that sends a message to the caller once the given
+//// computation is performed.
+////
+//// There are two important things to consider when using `async`:
+////
+//// 1. If you are using async tasks, you must await a reply as they are always
+//// sent.
+////
+//// 2. async tasks link the caller and the spawned process. This means that,
+//// if the caller crashes, the task will crash too and vice-versa. This is
+//// on purpose: if the process meant to receive the result no longer
+//// exists, there is no purpose in completing the computation.
+////
+//// This module is inspired by Elixir's [Task module][1].
+////
+//// [1]: https://hexdocs.pm/elixir/master/Task.html
+////
+
+// TODO: await_many
+import gleam/erlang/process.{type Pid, type ProcessMonitor, type Selector}
+import gleam/dynamic.{type Dynamic}
+
+pub opaque type Task(value) {
+ Task(
+ owner: Pid,
+ pid: Pid,
+ monitor: ProcessMonitor,
+ selector: Selector(Message(value)),
+ )
+}
+
+// TODO: test
+/// Spawn a task process that calls a given function in order to perform some
+/// work. The result of this function is send back to the parent and can be
+/// received using the `await` function.
+///
+/// See the top level module documentation for more information on async/await.
+///
+pub fn async(work: fn() -> value) -> Task(value) {
+ let owner = process.self()
+ let subject = process.new_subject()
+ let pid =
+ process.start(linked: True, running: fn() { process.send(subject, work()) })
+ let monitor = process.monitor_process(pid)
+ let selector =
+ process.new_selector()
+ |> process.selecting_process_down(monitor, FromMonitor)
+ |> process.selecting(subject, FromSubject)
+ Task(owner: owner, pid: pid, monitor: monitor, selector: selector)
+}
+
+pub type AwaitError {
+ Timeout
+ Exit(reason: Dynamic)
+}
+
+// We can only wait on a task if we are the owner of it so crash if we are
+// waiting on a task we don't own.
+fn assert_owner(task: Task(a)) -> Nil {
+ let self = process.self()
+ case task.owner == self {
+ True -> Nil
+ False ->
+ process.send_abnormal_exit(
+ self,
+ "awaited on a task that does not belong to this process",
+ )
+ }
+}
+
+type Message(value) {
+ FromMonitor(process.ProcessDown)
+ FromSubject(value)
+}
+
+// TODO: test
+/// Wait for the value computed by a task.
+///
+/// If the a value is not received before the timeout has elapsed or if the
+/// task process crashes then an error is returned.
+///
+pub fn try_await(task: Task(value), timeout: Int) -> Result(value, AwaitError) {
+ assert_owner(task)
+ case process.select(task.selector, timeout) {
+ // The task process has sent back a value
+ Ok(FromSubject(x)) -> {
+ process.demonitor_process(task.monitor)
+ Ok(x)
+ }
+
+ // The task process crashed without sending a value
+ Ok(FromMonitor(process.ProcessDown(reason: reason, ..))) ->
+ Error(Exit(reason))
+
+ // The task process is alive but has not sent a value yet
+ Error(Nil) -> Error(Timeout)
+ }
+}
+
+// TODO: test
+/// Wait for the value computed by a task.
+///
+/// If the a value is not received before the timeout has elapsed or if the
+/// task process crashes then this function crashes.
+///
+pub fn await(task: Task(value), timeout: Int) -> value {
+ let assert Ok(value) = try_await(task, timeout)
+ value
+}
+
+/// Wait endlessly for the value computed by a task.
+///
+/// Be Careful! This function does not return until there is a value to
+/// receive. If a value is not received then the process will be stuck waiting
+/// forever.
+///
+pub fn try_await_forever(task: Task(value)) -> Result(value, AwaitError) {
+ assert_owner(task)
+ case process.select_forever(task.selector) {
+ // The task process has sent back a value
+ FromSubject(x) -> {
+ process.demonitor_process(task.monitor)
+ Ok(x)
+ }
+
+ // The task process crashed without sending a value
+ FromMonitor(process.ProcessDown(reason: reason, ..)) -> Error(Exit(reason))
+ }
+}
+
+/// Wait endlessly for the value computed by a task.
+///
+/// Be Careful! Like `try_await_forever`, this function does not return until there is a value to
+/// receive.
+///
+/// If the task process crashes then this function crashes.
+///
+pub fn await_forever(task: Task(value)) -> value {
+ let assert Ok(value) = try_await_forever(task)
+ value
+}
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@actor.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@actor.erl
new file mode 100644
index 0000000..0606147
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam@otp@actor.erl
@@ -0,0 +1,273 @@
+-module(gleam@otp@actor).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([continue/1, with_selector/2, to_erlang_start_result/1, start_spec/1, start/2, send/2, call/3]).
+-export_type([message/1, next/2, init_result/2, self/2, spec/2, start_error/0, start_init_message/1]).
+
+-type message(GAS) :: {message, GAS} |
+ {system, gleam@otp@system:system_message()} |
+ {unexpected, gleam@dynamic:dynamic_()}.
+
+-type next(GAT, GAU) :: {continue,
+ GAU,
+ gleam@option:option(gleam@erlang@process:selector(GAT))} |
+ {stop, gleam@erlang@process:exit_reason()}.
+
+-type init_result(GAV, GAW) :: {ready, GAV, gleam@erlang@process:selector(GAW)} |
+ {failed, binary()}.
+
+-type self(GAX, GAY) :: {self,
+ gleam@otp@system:mode(),
+ gleam@erlang@process:pid_(),
+ GAX,
+ gleam@erlang@process:subject(GAY),
+ gleam@erlang@process:selector(message(GAY)),
+ gleam@otp@system:debug_state(),
+ fun((GAY, GAX) -> next(GAY, GAX))}.
+
+-type spec(GAZ, GBA) :: {spec,
+ fun(() -> init_result(GAZ, GBA)),
+ integer(),
+ fun((GBA, GAZ) -> next(GBA, GAZ))}.
+
+-type start_error() :: init_timeout |
+ {init_failed, gleam@erlang@process:exit_reason()} |
+ {init_crashed, gleam@dynamic:dynamic_()}.
+
+-type start_init_message(GBB) :: {ack,
+ {ok, gleam@erlang@process:subject(GBB)} |
+ {error, gleam@erlang@process:exit_reason()}} |
+ {mon, gleam@erlang@process:process_down()}.
+
+-spec continue(GBI) -> next(any(), GBI).
+continue(State) ->
+ {continue, State, none}.
+
+-spec with_selector(next(GBM, GBN), gleam@erlang@process:selector(GBM)) -> next(GBM, GBN).
+with_selector(Value, Selector) ->
+ case Value of
+ {continue, State, _} ->
+ {continue, State, {some, Selector}};
+
+ _ ->
+ Value
+ end.
+
+-spec exit_process(gleam@erlang@process:exit_reason()) -> gleam@erlang@process:exit_reason().
+exit_process(Reason) ->
+ Reason.
+
+-spec selecting_system_messages(gleam@erlang@process:selector(message(GBY))) -> gleam@erlang@process:selector(message(GBY)).
+selecting_system_messages(Selector) ->
+ _pipe = Selector,
+ gleam@erlang@process:selecting_record3(
+ _pipe,
+ erlang:binary_to_atom(<<"system"/utf8>>),
+ fun gleam_otp_external:convert_system_message/2
+ ).
+
+-spec receive_message(self(any(), GBU)) -> message(GBU).
+receive_message(Self) ->
+ Selector = case erlang:element(2, Self) of
+ suspended ->
+ _pipe = gleam_erlang_ffi:new_selector(),
+ selecting_system_messages(_pipe);
+
+ running ->
+ _pipe@1 = gleam_erlang_ffi:new_selector(),
+ _pipe@2 = gleam@erlang@process:selecting_anything(
+ _pipe@1,
+ fun(Field@0) -> {unexpected, Field@0} end
+ ),
+ _pipe@3 = gleam_erlang_ffi:merge_selector(
+ _pipe@2,
+ erlang:element(6, Self)
+ ),
+ selecting_system_messages(_pipe@3)
+ end,
+ gleam_erlang_ffi:select(Selector).
+
+-spec process_status_info(self(any(), any())) -> gleam@otp@system:status_info().
+process_status_info(Self) ->
+ {status_info,
+ erlang:binary_to_atom(<<"gleam@otp@actor"/utf8>>),
+ erlang:element(3, Self),
+ erlang:element(2, Self),
+ erlang:element(7, Self),
+ gleam@dynamic:from(erlang:element(4, Self))}.
+
+-spec init_selector(
+ gleam@erlang@process:subject(GGN),
+ gleam@erlang@process:selector(GGN)
+) -> gleam@erlang@process:selector(message(GGN)).
+init_selector(Subject, Selector) ->
+ _pipe = gleam_erlang_ffi:new_selector(),
+ _pipe@1 = gleam@erlang@process:selecting(
+ _pipe,
+ Subject,
+ fun(Field@0) -> {message, Field@0} end
+ ),
+ gleam_erlang_ffi:merge_selector(
+ _pipe@1,
+ gleam_erlang_ffi:map_selector(
+ Selector,
+ fun(Field@0) -> {message, Field@0} end
+ )
+ ).
+
+-spec loop(self(any(), any())) -> gleam@erlang@process:exit_reason().
+loop(Self) ->
+ case receive_message(Self) of
+ {system, System} ->
+ case System of
+ {get_state, Callback} ->
+ Callback(gleam@dynamic:from(erlang:element(4, Self))),
+ loop(Self);
+
+ {resume, Callback@1} ->
+ Callback@1(),
+ loop(erlang:setelement(2, Self, running));
+
+ {suspend, Callback@2} ->
+ Callback@2(),
+ loop(erlang:setelement(2, Self, suspended));
+
+ {get_status, Callback@3} ->
+ Callback@3(process_status_info(Self)),
+ loop(Self)
+ end;
+
+ {unexpected, Message} ->
+ logger:warning(
+ unicode:characters_to_list(
+ <<"Actor discarding unexpected message: ~s"/utf8>>
+ ),
+ [unicode:characters_to_list(gleam@string:inspect(Message))]
+ ),
+ loop(Self);
+
+ {message, Msg} ->
+ case (erlang:element(8, Self))(Msg, erlang:element(4, Self)) of
+ {stop, Reason} ->
+ exit_process(Reason);
+
+ {continue, State, New_selector} ->
+ Selector = begin
+ _pipe = New_selector,
+ _pipe@1 = gleam@option:map(
+ _pipe,
+ fun(_capture) ->
+ init_selector(erlang:element(5, Self), _capture)
+ end
+ ),
+ gleam@option:unwrap(_pipe@1, erlang:element(6, Self))
+ end,
+ loop(
+ erlang:setelement(
+ 6,
+ erlang:setelement(4, Self, State),
+ Selector
+ )
+ )
+ end
+ end.
+
+-spec initialise_actor(
+ spec(any(), GCP),
+ gleam@erlang@process:subject({ok, gleam@erlang@process:subject(GCP)} |
+ {error, gleam@erlang@process:exit_reason()})
+) -> gleam@erlang@process:exit_reason().
+initialise_actor(Spec, Ack) ->
+ Subject = gleam@erlang@process:new_subject(),
+ case (erlang:element(2, Spec))() of
+ {ready, State, Selector} ->
+ Selector@1 = init_selector(Subject, Selector),
+ gleam@erlang@process:send(Ack, {ok, Subject}),
+ Self = {self,
+ running,
+ gleam@erlang@process:subject_owner(Ack),
+ State,
+ Subject,
+ Selector@1,
+ sys:debug_options([]),
+ erlang:element(4, Spec)},
+ loop(Self);
+
+ {failed, Reason} ->
+ gleam@erlang@process:send(Ack, {error, {abnormal, Reason}}),
+ exit_process({abnormal, Reason})
+ end.
+
+-spec to_erlang_start_result(
+ {ok, gleam@erlang@process:subject(any())} | {error, start_error()}
+) -> {ok, gleam@erlang@process:pid_()} | {error, gleam@dynamic:dynamic_()}.
+to_erlang_start_result(Res) ->
+ case Res of
+ {ok, X} ->
+ {ok, gleam@erlang@process:subject_owner(X)};
+
+ {error, X@1} ->
+ {error, gleam@dynamic:from(X@1)}
+ end.
+
+-spec start_spec(spec(any(), GDD)) -> {ok, gleam@erlang@process:subject(GDD)} |
+ {error, start_error()}.
+start_spec(Spec) ->
+ Ack_subject = gleam@erlang@process:new_subject(),
+ Child = gleam@erlang@process:start(
+ fun() -> initialise_actor(Spec, Ack_subject) end,
+ true
+ ),
+ Monitor = gleam@erlang@process:monitor_process(Child),
+ Selector = begin
+ _pipe = gleam_erlang_ffi:new_selector(),
+ _pipe@1 = gleam@erlang@process:selecting(
+ _pipe,
+ Ack_subject,
+ fun(Field@0) -> {ack, Field@0} end
+ ),
+ gleam@erlang@process:selecting_process_down(
+ _pipe@1,
+ Monitor,
+ fun(Field@0) -> {mon, Field@0} end
+ )
+ end,
+ Result = case gleam_erlang_ffi:select(Selector, erlang:element(3, Spec)) of
+ {ok, {ack, {ok, Channel}}} ->
+ {ok, Channel};
+
+ {ok, {ack, {error, Reason}}} ->
+ {error, {init_failed, Reason}};
+
+ {ok, {mon, Down}} ->
+ {error, {init_crashed, erlang:element(3, Down)}};
+
+ {error, nil} ->
+ gleam@erlang@process:kill(Child),
+ {error, init_timeout}
+ end,
+ gleam_erlang_ffi:demonitor(Monitor),
+ Result.
+
+-spec start(GDJ, fun((GDK, GDJ) -> next(GDK, GDJ))) -> {ok,
+ gleam@erlang@process:subject(GDK)} |
+ {error, start_error()}.
+start(State, Loop) ->
+ start_spec(
+ {spec,
+ fun() -> {ready, State, gleam_erlang_ffi:new_selector()} end,
+ 5000,
+ Loop}
+ ).
+
+-spec send(gleam@erlang@process:subject(GDQ), GDQ) -> nil.
+send(Subject, Msg) ->
+ gleam@erlang@process:send(Subject, Msg).
+
+-spec call(
+ gleam@erlang@process:subject(GDS),
+ fun((gleam@erlang@process:subject(GDU)) -> GDS),
+ integer()
+) -> GDU.
+call(Selector, Make_message, Timeout) ->
+ gleam@erlang@process:call(Selector, Make_message, Timeout).
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@intensity_tracker.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@intensity_tracker.erl
new file mode 100644
index 0000000..8792f14
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam@otp@intensity_tracker.erl
@@ -0,0 +1,53 @@
+-module(gleam@otp@intensity_tracker).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([new/2, trim_window/3, add_event/1]).
+-export_type([intensity_tracker/0, too_intense/0]).
+
+-opaque intensity_tracker() :: {intensity_tracker,
+ integer(),
+ integer(),
+ list(integer())}.
+
+-type too_intense() :: too_intense.
+
+-spec new(integer(), integer()) -> intensity_tracker().
+new(Limit, Period) ->
+ {intensity_tracker, Limit, Period, []}.
+
+-spec now_seconds() -> integer().
+now_seconds() ->
+ erlang:monotonic_time(1).
+
+-spec trim_window(list(integer()), integer(), integer()) -> list(integer()).
+trim_window(Events, Now, Period) ->
+ case Events of
+ [] ->
+ [];
+
+ [Event | Events@1] ->
+ case Now >= (Event + Period) of
+ true ->
+ [Event | trim_window(Events@1, Now, Period)];
+
+ false ->
+ []
+ end
+ end.
+
+-spec add_event(intensity_tracker()) -> {ok, intensity_tracker()} |
+ {error, too_intense()}.
+add_event(Tracker) ->
+ Now = now_seconds(),
+ Events = trim_window(
+ [Now | erlang:element(4, Tracker)],
+ Now,
+ erlang:element(3, Tracker)
+ ),
+ case gleam@list:length(Events) >= erlang:element(2, Tracker) of
+ true ->
+ {error, too_intense};
+
+ false ->
+ {ok, erlang:setelement(4, Tracker, Events)}
+ end.
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@port.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@port.erl
new file mode 100644
index 0000000..b205739
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam@otp@port.erl
@@ -0,0 +1,8 @@
+-module(gleam@otp@port).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export_type([port_/0]).
+
+-type port_() :: any().
+
+
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@supervisor.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@supervisor.erl
new file mode 100644
index 0000000..39118f1
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam@otp@supervisor.erl
@@ -0,0 +1,322 @@
+-module(gleam@otp@supervisor).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([add/2, supervisor/1, worker/1, returning/2, start_spec/1, start/1, application_stopped/0, to_erlang_start_result/1]).
+-export_type([spec/2, children/1, child_spec/3, child_start_error/0, message/0, instruction/0, state/1, starter/1, child/1, handle_exit_error/0, application_start_mode/0, application_stop/0]).
+
+-type spec(GLS, GLT) :: {spec,
+ GLS,
+ integer(),
+ integer(),
+ fun((children(GLS)) -> children(GLT))}.
+
+-opaque children(GLU) :: {ready, starter(GLU)} | {failed, child_start_error()}.
+
+-opaque child_spec(GLV, GLW, GLX) :: {child_spec,
+ fun((GLW) -> {ok, gleam@erlang@process:subject(GLV)} |
+ {error, gleam@otp@actor:start_error()}),
+ fun((GLW, gleam@erlang@process:subject(GLV)) -> GLX)}.
+
+-type child_start_error() :: {child_start_error,
+ gleam@option:option(gleam@erlang@process:pid_()),
+ gleam@otp@actor:start_error()}.
+
+-opaque message() :: {exit, gleam@erlang@process:exit_message()} |
+ {retry_restart, gleam@erlang@process:pid_()}.
+
+-type instruction() :: start_all | {start_from, gleam@erlang@process:pid_()}.
+
+-type state(GLY) :: {state,
+ gleam@otp@intensity_tracker:intensity_tracker(),
+ starter(GLY),
+ gleam@erlang@process:subject(gleam@erlang@process:pid_())}.
+
+-type starter(GLZ) :: {starter,
+ GLZ,
+ gleam@option:option(fun((instruction()) -> {ok,
+ {starter(GLZ), instruction()}} |
+ {error, child_start_error()}))}.
+
+-type child(GMA) :: {child, gleam@erlang@process:pid_(), GMA}.
+
+-type handle_exit_error() :: {restart_failed,
+ gleam@erlang@process:pid_(),
+ gleam@otp@intensity_tracker:intensity_tracker()} |
+ too_many_restarts.
+
+-type application_start_mode() :: normal |
+ {takeover, gleam@erlang@node:node_()} |
+ {failover, gleam@erlang@node:node_()}.
+
+-type application_stop() :: any().
+
+-spec start_child(child_spec(any(), GME, GMF), GME) -> {ok, child(GMF)} |
+ {error, child_start_error()}.
+start_child(Child_spec, Argument) ->
+ gleam@result:then(
+ begin
+ _pipe = (erlang:element(2, Child_spec))(Argument),
+ gleam@result:map_error(
+ _pipe,
+ fun(_capture) -> {child_start_error, none, _capture} end
+ )
+ end,
+ fun(Subject) ->
+ {ok,
+ {child,
+ gleam@erlang@process:subject_owner(Subject),
+ (erlang:element(3, Child_spec))(Argument, Subject)}}
+ end
+ ).
+
+-spec shutdown_child(
+ gleam@erlang@process:pid_(),
+ child_spec(any(), any(), any())
+) -> nil.
+shutdown_child(Pid, _) ->
+ gleam@erlang@process:send_exit(Pid).
+
+-spec perform_instruction_for_child(
+ GMS,
+ instruction(),
+ child_spec(any(), GMS, GMU),
+ child(GMU)
+) -> {ok, {child(GMU), instruction()}} | {error, child_start_error()}.
+perform_instruction_for_child(Argument, Instruction, Child_spec, Child) ->
+ Current = erlang:element(2, Child),
+ case Instruction of
+ {start_from, Target} when Target =/= Current ->
+ {ok, {Child, Instruction}};
+
+ _ ->
+ shutdown_child(Current, Child_spec),
+ gleam@result:then(
+ start_child(Child_spec, Argument),
+ fun(Child@1) -> {ok, {Child@1, start_all}} end
+ )
+ end.
+
+-spec add_child_to_starter(
+ starter(GNC),
+ child_spec(any(), GNC, GNF),
+ child(GNF)
+) -> starter(GNF).
+add_child_to_starter(Starter, Child_spec, Child) ->
+ Starter@3 = fun(Instruction) ->
+ gleam@result:then(case erlang:element(3, Starter) of
+ {some, Start} ->
+ Start(Instruction);
+
+ none ->
+ {ok, {Starter, Instruction}}
+ end, fun(_use0) ->
+ {Starter@1, Instruction@1} = _use0,
+ gleam@result:then(
+ perform_instruction_for_child(
+ erlang:element(2, Starter@1),
+ Instruction@1,
+ Child_spec,
+ Child
+ ),
+ fun(_use0@1) ->
+ {Child@1, Instruction@2} = _use0@1,
+ Starter@2 = add_child_to_starter(
+ Starter@1,
+ Child_spec,
+ Child@1
+ ),
+ {ok, {Starter@2, Instruction@2}}
+ end
+ )
+ end)
+ end,
+ {starter, erlang:element(3, Child), {some, Starter@3}}.
+
+-spec start_and_add_child(starter(GNL), child_spec(any(), GNL, GNO)) -> children(GNO).
+start_and_add_child(State, Child_spec) ->
+ case start_child(Child_spec, erlang:element(2, State)) of
+ {ok, Child} ->
+ {ready, add_child_to_starter(State, Child_spec, Child)};
+
+ {error, Reason} ->
+ {failed, Reason}
+ end.
+
+-spec add(children(GNT), child_spec(any(), GNT, GNW)) -> children(GNW).
+add(Children, Child_spec) ->
+ case Children of
+ {failed, Fail} ->
+ {failed, Fail};
+
+ {ready, State} ->
+ start_and_add_child(State, Child_spec)
+ end.
+
+-spec supervisor(
+ fun((GOB) -> {ok, gleam@erlang@process:subject(GOC)} |
+ {error, gleam@otp@actor:start_error()})
+) -> child_spec(GOC, GOB, GOB).
+supervisor(Start) ->
+ {child_spec, Start, fun(Argument, _) -> Argument end}.
+
+-spec worker(
+ fun((GOJ) -> {ok, gleam@erlang@process:subject(GOK)} |
+ {error, gleam@otp@actor:start_error()})
+) -> child_spec(GOK, GOJ, GOJ).
+worker(Start) ->
+ {child_spec, Start, fun(Argument, _) -> Argument end}.
+
+-spec returning(
+ child_spec(GOR, GOS, any()),
+ fun((GOS, gleam@erlang@process:subject(GOR)) -> GOY)
+) -> child_spec(GOR, GOS, GOY).
+returning(Child, Updater) ->
+ {child_spec, erlang:element(2, Child), Updater}.
+
+-spec init(spec(any(), GPD)) -> gleam@otp@actor:init_result(state(GPD), message()).
+init(Spec) ->
+ Retry = gleam@erlang@process:new_subject(),
+ gleam_erlang_ffi:trap_exits(true),
+ Selector = begin
+ _pipe = gleam_erlang_ffi:new_selector(),
+ _pipe@1 = gleam@erlang@process:selecting(
+ _pipe,
+ Retry,
+ fun(Field@0) -> {retry_restart, Field@0} end
+ ),
+ gleam@erlang@process:selecting_trapped_exits(
+ _pipe@1,
+ fun(Field@0) -> {exit, Field@0} end
+ )
+ end,
+ Result = begin
+ _pipe@2 = {starter, erlang:element(2, Spec), none},
+ _pipe@3 = {ready, _pipe@2},
+ (erlang:element(5, Spec))(_pipe@3)
+ end,
+ case Result of
+ {ready, Starter} ->
+ Restarts = gleam@otp@intensity_tracker:new(
+ erlang:element(3, Spec),
+ erlang:element(4, Spec)
+ ),
+ State = {state, Restarts, Starter, Retry},
+ {ready, State, Selector};
+
+ {failed, Error} ->
+ {failed, case erlang:element(3, Error) of
+ init_timeout ->
+ <<"Child initialisation timed out"/utf8>>;
+
+ {init_crashed, Reason} ->
+ gleam@string:append(
+ <<"Child crashed during initialisation: "/utf8>>,
+ gleam@string:inspect(Reason)
+ );
+
+ {init_failed, Reason@1} ->
+ gleam@string:append(
+ <<"Child failed to start during initialisation: "/utf8>>,
+ gleam@string:inspect(Reason@1)
+ )
+ end}
+ end.
+
+-spec handle_exit(gleam@erlang@process:pid_(), state(GPJ)) -> gleam@otp@actor:next(message(), state(GPJ)).
+handle_exit(Pid, State) ->
+ Outcome = begin
+ _assert_subject = erlang:element(3, erlang:element(3, State)),
+ {some, Start} = case _assert_subject of
+ {some, _} -> _assert_subject;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"gleam/otp/supervisor"/utf8>>,
+ function => <<"handle_exit"/utf8>>,
+ line => 293})
+ end,
+ gleam@result:then(
+ begin
+ _pipe = erlang:element(2, State),
+ _pipe@1 = gleam@otp@intensity_tracker:add_event(_pipe),
+ gleam@result:map_error(_pipe@1, fun(_) -> too_many_restarts end)
+ end,
+ fun(Restarts) ->
+ gleam@result:then(
+ begin
+ _pipe@2 = Start({start_from, Pid}),
+ gleam@result:map_error(
+ _pipe@2,
+ fun(E) ->
+ {restart_failed,
+ gleam@option:unwrap(
+ erlang:element(2, E),
+ Pid
+ ),
+ Restarts}
+ end
+ )
+ end,
+ fun(_use0) ->
+ {Starter, _} = _use0,
+ {ok,
+ erlang:setelement(
+ 2,
+ erlang:setelement(3, State, Starter),
+ Restarts
+ )}
+ end
+ )
+ end
+ )
+ end,
+ case Outcome of
+ {ok, State@1} ->
+ gleam@otp@actor:continue(State@1);
+
+ {error, {restart_failed, Failed_child, Restarts@1}} ->
+ gleam@erlang@process:send(erlang:element(4, State), Failed_child),
+ State@2 = erlang:setelement(2, State, Restarts@1),
+ gleam@otp@actor:continue(State@2);
+
+ {error, too_many_restarts} ->
+ {stop,
+ {abnormal,
+ <<"Child processes restarted too many times within allowed period"/utf8>>}}
+ end.
+
+-spec loop(message(), state(GPO)) -> gleam@otp@actor:next(message(), state(GPO)).
+loop(Message, State) ->
+ case Message of
+ {exit, Exit_message} ->
+ handle_exit(erlang:element(2, Exit_message), State);
+
+ {retry_restart, Pid} ->
+ handle_exit(Pid, State)
+ end.
+
+-spec start_spec(spec(any(), any())) -> {ok,
+ gleam@erlang@process:subject(message())} |
+ {error, gleam@otp@actor:start_error()}.
+start_spec(Spec) ->
+ gleam@otp@actor:start_spec(
+ {spec, fun() -> init(Spec) end, 60000, fun loop/2}
+ ).
+
+-spec start(fun((children(nil)) -> children(any()))) -> {ok,
+ gleam@erlang@process:subject(message())} |
+ {error, gleam@otp@actor:start_error()}.
+start(Init) ->
+ start_spec({spec, nil, 1, 5, Init}).
+
+-spec application_stopped() -> application_stop().
+application_stopped() ->
+ gleam_otp_external:application_stopped().
+
+-spec to_erlang_start_result(
+ {ok, gleam@erlang@process:subject(any())} |
+ {error, gleam@otp@actor:start_error()}
+) -> {ok, gleam@erlang@process:pid_()} | {error, gleam@dynamic:dynamic_()}.
+to_erlang_start_result(Res) ->
+ gleam@otp@actor:to_erlang_start_result(Res).
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@system.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@system.erl
new file mode 100644
index 0000000..622e5ea
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam@otp@system.erl
@@ -0,0 +1,43 @@
+-module(gleam@otp@system).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([debug_state/1, get_state/1, suspend/1, resume/1]).
+-export_type([mode/0, debug_option/0, debug_state/0, status_info/0, system_message/0, do_not_leak/0]).
+
+-type mode() :: running | suspended.
+
+-type debug_option() :: no_debug.
+
+-type debug_state() :: any().
+
+-type status_info() :: {status_info,
+ gleam@erlang@atom:atom_(),
+ gleam@erlang@process:pid_(),
+ mode(),
+ debug_state(),
+ gleam@dynamic:dynamic_()}.
+
+-type system_message() :: {resume, fun(() -> nil)} |
+ {suspend, fun(() -> nil)} |
+ {get_state, fun((gleam@dynamic:dynamic_()) -> nil)} |
+ {get_status, fun((status_info()) -> nil)}.
+
+-type do_not_leak() :: any().
+
+-spec debug_state(list(debug_option())) -> debug_state().
+debug_state(A) ->
+ sys:debug_options(A).
+
+-spec get_state(gleam@erlang@process:pid_()) -> gleam@dynamic:dynamic_().
+get_state(From) ->
+ sys:get_state(From).
+
+-spec suspend(gleam@erlang@process:pid_()) -> nil.
+suspend(Pid) ->
+ sys:suspend(Pid),
+ nil.
+
+-spec resume(gleam@erlang@process:pid_()) -> nil.
+resume(Pid) ->
+ sys:resume(Pid),
+ nil.
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam@otp@task.erl b/aoc2023/build/packages/gleam_otp/src/gleam@otp@task.erl
new file mode 100644
index 0000000..e004284
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam@otp@task.erl
@@ -0,0 +1,111 @@
+-module(gleam@otp@task).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([async/1, try_await/2, await/2, try_await_forever/1, await_forever/1]).
+-export_type([task/1, await_error/0, message/1]).
+
+-opaque task(FWJ) :: {task,
+ gleam@erlang@process:pid_(),
+ gleam@erlang@process:pid_(),
+ gleam@erlang@process:process_monitor(),
+ gleam@erlang@process:selector(message(FWJ))}.
+
+-type await_error() :: timeout | {exit, gleam@dynamic:dynamic_()}.
+
+-type message(FWK) :: {from_monitor, gleam@erlang@process:process_down()} |
+ {from_subject, FWK}.
+
+-spec async(fun(() -> FWL)) -> task(FWL).
+async(Work) ->
+ Owner = erlang:self(),
+ Subject = gleam@erlang@process:new_subject(),
+ Pid = gleam@erlang@process:start(
+ fun() -> gleam@erlang@process:send(Subject, Work()) end,
+ true
+ ),
+ Monitor = gleam@erlang@process:monitor_process(Pid),
+ Selector = begin
+ _pipe = gleam_erlang_ffi:new_selector(),
+ _pipe@1 = gleam@erlang@process:selecting_process_down(
+ _pipe,
+ Monitor,
+ fun(Field@0) -> {from_monitor, Field@0} end
+ ),
+ gleam@erlang@process:selecting(
+ _pipe@1,
+ Subject,
+ fun(Field@0) -> {from_subject, Field@0} end
+ )
+ end,
+ {task, Owner, Pid, Monitor, Selector}.
+
+-spec assert_owner(task(any())) -> nil.
+assert_owner(Task) ->
+ Self = erlang:self(),
+ case erlang:element(2, Task) =:= Self of
+ true ->
+ nil;
+
+ false ->
+ gleam@erlang@process:send_abnormal_exit(
+ Self,
+ <<"awaited on a task that does not belong to this process"/utf8>>
+ )
+ end.
+
+-spec try_await(task(FWP), integer()) -> {ok, FWP} | {error, await_error()}.
+try_await(Task, Timeout) ->
+ assert_owner(Task),
+ case gleam_erlang_ffi:select(erlang:element(5, Task), Timeout) of
+ {ok, {from_subject, X}} ->
+ gleam_erlang_ffi:demonitor(erlang:element(4, Task)),
+ {ok, X};
+
+ {ok, {from_monitor, {process_down, _, Reason}}} ->
+ {error, {exit, Reason}};
+
+ {error, nil} ->
+ {error, timeout}
+ end.
+
+-spec await(task(FWT), integer()) -> FWT.
+await(Task, Timeout) ->
+ _assert_subject = try_await(Task, Timeout),
+ {ok, Value} = case _assert_subject of
+ {ok, _} -> _assert_subject;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"gleam/otp/task"/utf8>>,
+ function => <<"await"/utf8>>,
+ line => 117})
+ end,
+ Value.
+
+-spec try_await_forever(task(FWV)) -> {ok, FWV} | {error, await_error()}.
+try_await_forever(Task) ->
+ assert_owner(Task),
+ case gleam_erlang_ffi:select(erlang:element(5, Task)) of
+ {from_subject, X} ->
+ gleam_erlang_ffi:demonitor(erlang:element(4, Task)),
+ {ok, X};
+
+ {from_monitor, {process_down, _, Reason}} ->
+ {error, {exit, Reason}}
+ end.
+
+-spec await_forever(task(FWZ)) -> FWZ.
+await_forever(Task) ->
+ _assert_subject = try_await_forever(Task),
+ {ok, Value} = case _assert_subject of
+ {ok, _} -> _assert_subject;
+ _assert_fail ->
+ erlang:error(#{gleam_error => let_assert,
+ message => <<"Assertion pattern match failed"/utf8>>,
+ value => _assert_fail,
+ module => <<"gleam/otp/task"/utf8>>,
+ function => <<"await_forever"/utf8>>,
+ line => 149})
+ end,
+ Value.
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam_otp.app.src b/aoc2023/build/packages/gleam_otp/src/gleam_otp.app.src
new file mode 100644
index 0000000..5c52295
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam_otp.app.src
@@ -0,0 +1,15 @@
+{application, gleam_otp, [
+ {vsn, "0.8.0"},
+ {applications, [gleam_erlang,
+ gleam_stdlib,
+ gleeunit]},
+ {description, "Fault tolerant multicore Gleam programs with OTP"},
+ {modules, [gleam@otp@actor,
+ gleam@otp@intensity_tracker,
+ gleam@otp@port,
+ gleam@otp@supervisor,
+ gleam@otp@system,
+ gleam@otp@task,
+ gleam_otp]},
+ {registered, []}
+]}.
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam_otp.erl b/aoc2023/build/packages/gleam_otp/src/gleam_otp.erl
new file mode 100644
index 0000000..9381ad2
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam_otp.erl
@@ -0,0 +1,28 @@
+-module(gleam_otp).
+-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function]).
+
+-export([main/0]).
+
+-spec spawn_task(integer()) -> gleam@otp@task:task(nil).
+spawn_task(I) ->
+ gleam@otp@task:async(fun() -> case (I rem 500) =:= 0 of
+ true ->
+ gleam@io:println(
+ <<"Hello from "/utf8, (gleam@int:to_string(I))/binary>>
+ );
+
+ false ->
+ nil
+ end end).
+
+-spec main() -> integer().
+main() ->
+ gleam@io:debug(
+ gleam_otp_test_external:get_message_queue_length(erlang:self())
+ ),
+ _pipe = gleam@list:range(0, 1000000),
+ _pipe@1 = gleam@list:map(_pipe, fun spawn_task/1),
+ gleam@list:each(_pipe@1, fun gleam@otp@task:await_forever/1),
+ gleam@io:debug(
+ gleam_otp_test_external:get_message_queue_length(erlang:self())
+ ).
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam_otp.gleam b/aoc2023/build/packages/gleam_otp/src/gleam_otp.gleam
new file mode 100644
index 0000000..69cdd5b
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam_otp.gleam
@@ -0,0 +1,27 @@
+import gleam/io
+import gleam/int
+import gleam/list
+import gleam/otp/task
+import gleam/erlang/process.{type Pid}
+
+@external(erlang, "gleam_otp_test_external", "get_message_queue_length")
+fn get_message_queue_length(pid pid: Pid) -> Int
+
+fn spawn_task(i) {
+ task.async(fn() {
+ case i % 500 == 0 {
+ True -> io.println("Hello from " <> int.to_string(i))
+ False -> Nil
+ }
+ })
+}
+
+pub fn main() {
+ io.debug(get_message_queue_length(process.self()))
+
+ list.range(0, 1_000_000)
+ |> list.map(spawn_task)
+ |> list.each(task.await_forever)
+
+ io.debug(get_message_queue_length(process.self()))
+}
diff --git a/aoc2023/build/packages/gleam_otp/src/gleam_otp_external.erl b/aoc2023/build/packages/gleam_otp/src/gleam_otp_external.erl
new file mode 100644
index 0000000..8910a67
--- /dev/null
+++ b/aoc2023/build/packages/gleam_otp/src/gleam_otp_external.erl
@@ -0,0 +1,43 @@
+-module(gleam_otp_external).
+
+-export([application_stopped/0, convert_system_message/2]).
+
+% TODO: support other system messages
+% {replace_state, StateFn}
+% {change_code, Mod, Vsn, Extra}
+% {terminate, Reason}
+% {debug, {log, Flag}}
+% {debug, {trace, Flag}}
+% {debug, {log_to_file, FileName}}
+% {debug, {statistics, Flag}}
+% {debug, no_debug}
+% {debug, {install, {Func, FuncState}}}
+% {debug, {install, {FuncId, Func, FuncState}}}
+% {debug, {remove, FuncOrId}}
+% GetStatus(Subject(StatusInfo))
+convert_system_message({From, Ref}, Request) when is_pid(From) ->
+ Reply = fun(Msg) ->
+ erlang:send(From, {Ref, Msg}),
+ nil
+ end,
+ System = fun(Callback) ->
+ {system, {Request, Callback}}
+ end,
+ case Request of
+ get_status -> System(fun(Status) -> Reply(process_status(Status)) end);
+ get_state -> System(Reply);
+ suspend -> System(fun() -> Reply(ok) end);
+ resume -> System(fun() -> Reply(ok) end);
+ Other -> {unexpeceted, Other}
+ end.
+
+process_status({status_info, Module, Parent, Mode, DebugState, State}) ->
+ Data = [
+ get(), Mode, Parent, DebugState,
+ [{header, "Status for Gleam process " ++ pid_to_list(self())},
+ {data, [{'Status', Mode}, {'Parent', Parent}, {'State', State}]}]
+ ],
+ {status, self(), {module, Module}, Data}.
+
+application_stopped() ->
+ ok.