aboutsummaryrefslogtreecommitdiff
path: root/docs/book/print.html
diff options
context:
space:
mode:
Diffstat (limited to 'docs/book/print.html')
-rw-r--r--docs/book/print.html1216
1 files changed, 1126 insertions, 90 deletions
diff --git a/docs/book/print.html b/docs/book/print.html
index 8803537..6361bdd 100644
--- a/docs/book/print.html
+++ b/docs/book/print.html
@@ -89,7 +89,7 @@
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
- <ol class="chapter"><li class="chapter-item expanded affix "><a href="../index.html">The Puck Programming Language</a></li><li class="chapter-item expanded "><a href="OVERVIEW.html"><strong aria-hidden="true">1.</strong> Basic Usage</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">1.1.</strong> Variables and Comments</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.2.</strong> Basic Types</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.3.</strong> Functions and Calls</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.4.</strong> Boolean and Integer Operations</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.5.</strong> Conditionals and Control Flow</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.6.</strong> Error Handling</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.7.</strong> Loops and Iterators</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.8.</strong> Modules</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.9.</strong> Compile-time Programming</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.10.</strong> Async and Threading</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.11.</strong> Advanced Types</div></li></ol></li><li class="chapter-item expanded "><a href="SYNTAX.html"><strong aria-hidden="true">2.</strong> Syntax</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">2.1.</strong> Indentation Rules [todo]</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">2.2.</strong> Reserved Keywords</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">2.3.</strong> A Formal Grammar</div></li></ol></li><li class="chapter-item expanded "><a href="TYPES.html"><strong aria-hidden="true">3.</strong> Type System</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">3.1.</strong> Basic Types</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">3.2.</strong> Parameter Types</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">3.3.</strong> Reference Types</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">3.4.</strong> Abstract Types</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">3.5.</strong> Advanced Types</div></li></ol></li><li class="chapter-item expanded "><a href="MODULES.html"><strong aria-hidden="true">4.</strong> Module System</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">4.1.</strong> Using Modules</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.2.</strong> Implicit Modules</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.3.</strong> Defining Module Interfaces [todo]</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.4.</strong> Defining an External API [todo]</div></li></ol></li><li class="chapter-item expanded "><a href="ERRORS.html"><strong aria-hidden="true">5.</strong> Error Handling</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">5.1.</strong> Errors as Monads</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">5.2.</strong> Errors as Catchable Exceptions</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">5.3.</strong> Errors and Void Functions [todo]</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">5.4.</strong> Unrecoverable Exceptions</div></li></ol></li><li class="chapter-item expanded "><a href="ASYNC.html"><strong aria-hidden="true">6.</strong> Async System</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">6.1.</strong> Threading [todo]</div></li></ol></li><li class="chapter-item expanded "><a href="METAPROGRAMMING.html"><strong aria-hidden="true">7.</strong> Metaprogramming</a></li><li class="chapter-item expanded "><div><strong aria-hidden="true">8.</strong> Memory Management [todo]</div></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">8.1.</strong> Reference Counting Optimizations</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">8.2.</strong> Annotations and Ownership</div></li></ol></li><li class="chapter-item expanded "><a href="INTEROP.html"><strong aria-hidden="true">9.</strong> Language Interop [draft]</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">9.1.</strong> Rust, Swift, Nim</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">9.2.</strong> Java, Kotlin</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">9.3.</strong> Python, Racket, C</div></li></ol></li><li class="chapter-item expanded "><div><strong aria-hidden="true">10.</strong> Refinement Types [draft]</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">11.</strong> Dependent Types [draft]</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">12.</strong> Effects System [draft]</div></li></ol>
+ <ol class="chapter"><li class="chapter-item expanded affix "><a href="../index.html">The Puck Programming Language</a></li><li class="chapter-item expanded "><a href="OVERVIEW.html"><strong aria-hidden="true">1.</strong> Basic Usage</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">1.1.</strong> Variables and Comments</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.2.</strong> Functions and Indentation</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.3.</strong> Uniform Function Call Syntax</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.4.</strong> Basic Types</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.5.</strong> Conditionals and Pattern Matching</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.6.</strong> Error Handling</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.7.</strong> Blocks and Loops</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.8.</strong> Module System</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.9.</strong> Compile-time Programming</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.10.</strong> Async System and Threading</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.11.</strong> Memory Management</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.12.</strong> Types System</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.13.</strong> Structs and Tuples</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.14.</strong> Unions and Enums</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">1.15.</strong> Classes</div></li></ol></li><li class="chapter-item expanded "><a href="SYNTAX.html"><strong aria-hidden="true">2.</strong> Syntax</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">2.1.</strong> Call Syntax</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">2.2.</strong> Indentation Rules</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">2.3.</strong> Expression Rules</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">2.4.</strong> Reserved Keywords</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">2.5.</strong> A Formal Grammar</div></li></ol></li><li class="chapter-item expanded "><a href="TYPES.html"><strong aria-hidden="true">3.</strong> Type System</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">3.1.</strong> Basic Types</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">3.2.</strong> Parameter Types</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">3.3.</strong> Reference Types</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">3.4.</strong> Abstract Types</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">3.5.</strong> Advanced Types</div></li></ol></li><li class="chapter-item expanded "><a href="MODULES.html"><strong aria-hidden="true">4.</strong> Module System</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">4.1.</strong> What are modules?</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.2.</strong> Using modules</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.3.</strong> Implicit modules</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.4.</strong> Defining interfaces [todo]</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">4.5.</strong> Defining an external API [todo]</div></li></ol></li><li class="chapter-item expanded "><a href="MEMORY_MANAGEMENT.html"><strong aria-hidden="true">5.</strong> Memory Management [todo]</a></li><li class="chapter-item expanded "><a href="METAPROGRAMMING.html"><strong aria-hidden="true">6.</strong> Metaprogramming</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">6.1.</strong> Scope</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">6.2.</strong> Usage</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">6.3.</strong> Quoting [todo]</div></li></ol></li><li class="chapter-item expanded "><a href="ERRORS.html"><strong aria-hidden="true">7.</strong> Error Handling</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">7.1.</strong> Errors as monads</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">7.2.</strong> Errors as checked exceptions</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">7.3.</strong> Errors as effects [todo]</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">7.4.</strong> Unrecoverable Exceptions</div></li></ol></li><li class="chapter-item expanded "><a href="ASYNC.html"><strong aria-hidden="true">8.</strong> Async System</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">8.1.</strong> Effects System [todo]</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">8.2.</strong> Threading [todo]</div></li></ol></li><li class="chapter-item expanded "><a href="INTEROP.html"><strong aria-hidden="true">9.</strong> Language Interop [draft]</a></li><li><ol class="section"><li class="chapter-item expanded "><div><strong aria-hidden="true">9.1.</strong> Rust</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">9.2.</strong> Swift, Nim</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">9.3.</strong> Java, Kotlin</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">9.4.</strong> Python, Racket, C</div></li></ol></li><li class="chapter-item expanded "><div><strong aria-hidden="true">10.</strong> Effects System [draft]</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">11.</strong> Refinement Types [draft]</div></li><li class="chapter-item expanded "><div><strong aria-hidden="true">12.</strong> Dependent Types [draft]</div></li><li class="chapter-item expanded "><a href="EXAMPLES.html"><strong aria-hidden="true">13.</strong> Examples</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
@@ -285,21 +285,22 @@ pub mod tables =
Don't use it. Everything is unimplemented and it will break underneath your feet.</p>
<p>That said: in the future, once somewhat stabilized, reasons why you <em>would</em> use it would be for:</p>
<ul>
-<li>The <strong>syntax</strong>, aiming to be flexible, predictable, and succinct, through the use of <em>uniform function call syntax</em> and significant whitespace</li>
+<li>The <strong>syntax</strong>, aiming to be flexible, predictable, and succinct, through the use of <em>uniform function call syntax</em>, significant whitespace, and consistent scoping rules</li>
<li>The <strong>type system</strong>, being modern and powerful with a strong emphasis on safety, algebraic data types, optional and result types, first-class functions, generics, interfaces, and modules</li>
<li>The <strong>memory management system</strong>, implementing a model of strict ownership with an optimized reference counting escape hatch</li>
<li>The <strong>metaprogramming</strong>, providing integrated macros capable of rewriting the abstract syntax tree before or after typechecking</li>
-<li>The <strong>interop system</strong>, allowing foreign functions to be usable with native semantics from a bevy of languages</li>
+<li>The <strong>interop system</strong>, allowing foreign functions to be usable with native syntax/semantics from a bevy of other languages</li>
</ul>
<p>This is the language I keep in my head. It sprung from a series of unstructured notes I kept on language design, that finally became something more comprehensive in early 2023. The overarching goal is to provide a language capable of elegantly expressing any problem, and explore ownership and interop along the way.</p>
<h2 id="how-do-i-learn-more"><a class="header" href="#how-do-i-learn-more">How do I learn more?</a></h2>
<ul>
<li>The <a href="../docs/BASIC.html">basic usage</a> document lays out the fundamental semantics of Puck.</li>
<li>The <a href="../docs/SYNTAX.html">syntax</a> document provides a deeper and formal look into the grammar of Puck.</li>
-<li>The <a href="../docs/TYPES.html">type system</a> document gives an in-depth analysis of Puck's extensive type system. <!-- and its relationship to classes and other abstractions. --></li>
+<li>The <a href="../docs/TYPES.html">type system</a> document gives an in-depth analysis of Puck's extensive type system.</li>
<li>The <a href="../docs/MODULES.html">modules</a> document provides a more detailed look at the first-class module system.</li>
-<li>The <a href="../docs/MEMORY_MANAGEMENT.html">memory management</a> document gives an overview of Puck's memory model. <!-- which is considered a mashup of the models pioneered by Lobster, Rust, and Nim. --></li>
-<li>The <a href="../docs/METAPROGRAMMING.html">metaprogramming</a> document explains how using metaprogramming to extend the language works. <!-- and write more powerful code works. --></li>
+<li>The <a href="../docs/ERRORS.html">error handling</a> document gives a look at the various kinds of error handling available.</li>
+<li>The <a href="../docs/MEMORY_MANAGEMENT.html">memory management</a> document gives an overview of Puck's memory model.</li>
+<li>The <a href="../docs/METAPROGRAMMING.html">metaprogramming</a> document explains how using metaprogramming to extend the language works.</li>
<li>The <a href="../docs/ASYNC.html">asynchronous</a> document gives an overview of Puck's colourless asynchronous support.</li>
<li>The <a href="../docs/INTEROP.html">interop</a> document gives an overview of how the first-class language interop system works.</li>
<li>The <a href="../docs/STDLIB.html">standard library</a> document provides an overview and examples of usage of the standard library.</li>
@@ -307,7 +308,7 @@ Don't use it. Everything is unimplemented and it will break underneath your feet
</ul>
<p>These are best read in order.</p>
<p>Note that all of these documents (and parts of this README) are written as if everything already exists. Nothing already exists! You can see the <a href="../docs/ROADMAP.html">roadmap</a> for an actual sense as to the state of the language. I simply found writing in the present tense to be an easier way to collect my thoughts.</p>
-<p>This language does not currently integrate ideas from the following areas of active research: effects systems, refinement types, and dependent types. It plans to integrate refinement types in the future as a basis for <code>range[]</code> types, and to explore safety and optimizations surrounding integer overflow.</p>
+<p>This language does not currently integrate ideas from the following areas of active research: effects systems, refinement types, and dependent types. It plans to base (un)safety tracking, exception handling, and async/await on a future effects system. It plans to integrate refinement types in the future as a basis for <code>range[]</code> types, and to explore safety and optimizations surrounding integer overflow.</p>
<h2 id="primary-references"><a class="header" href="#primary-references">Primary References</a></h2>
<ul>
<li><a href="https://graydon2.dreamwidth.org/307291.html">The Rust I wanted had no future</a></li>
@@ -319,11 +320,11 @@ Don't use it. Everything is unimplemented and it will break underneath your feet
<p>It attempts to explore designs in making functional programming paradigms comfortable to those familiar with imperative and object-oriented languages, as well as deal with some more technical problems along the way, such as integrated refinement types and typesafe interop.</p>
<p>This is the language I keep in my head. It reflects the way I think and reason about code.</p>
<p>I do hope others enjoy it.</p>
-<h2 id="declarations-and-comments"><a class="header" href="#declarations-and-comments">Declarations and Comments</a></h2>
+<h2 id="variables-and-comments"><a class="header" href="#variables-and-comments">Variables and Comments</a></h2>
<pre><code class="language-puck">let ident: int = 413
# type annotations are optional
var phrase = "Hello, world!"
-const compile_time = when linux then "linux" else "windows"
+const compile_time = std.os.file_name
</code></pre>
<p>Variables may be mutable (<code>var</code>), immutable (<code>let</code>), or compile-time evaluated and immutable (<code>const</code>).
Type annotations on variables and other bindings follow the name of the binding (with <code>: Type</code>), and are typically optional.
@@ -332,7 +333,7 @@ The type system is comprehensive, and complex enough to warrant delaying full co
<ul>
<li><code>int</code>, <code>uint</code>: signed and unsigned integers
<ul>
-<li><code>i[\d+]</code>, <code>u[\d+]</code>: arbitrary fixed-size counterparts</li>
+<li><code>i[\d]+</code>, <code>u[\d]+</code>: arbitrary fixed-size counterparts</li>
</ul>
</li>
<li><code>float</code>, <code>decimal</code>: floating-point numbers
@@ -367,7 +368,7 @@ print [1, 2, 3].inc(1) # 2, 3, 4
print [1].len # 1
</code></pre>
<p>Puck supports <em>uniform function call syntax</em>: and so any function may be called using the typical syntax for method calls, that is, the first parameter of any function may be appended with a <code>.</code> and moved to precede it, in the style of a typical method. (There are no methods in Puck. All functions are statically dispatched. This may change in the future.)</p>
-<p>This allows for a number of syntactic cleanups. Arbitrary functions with compatible types may be chained with no need for a special pipe operator. Object field access, module member access, and function calls are unified, reducing the need for getters and setters. Given a first type, IDEs using dot-autocomplete can fill in all the functions defined for that type. Programmers from object-oriented languages may find the lack of classes more bearable. UFCS is implemented in shockingly few languages, and so Puck joins the tiny club that previously consisted of just D and Nim.</p>
+<p>This allows for a number of syntactic cleanups. Arbitrary functions with compatible types may be chained with no need for a special pipe operator. Object field access, module member access, and function calls are unified, reducing the need for getters and setters. Given a first type, IDEs using dot-autocomplete can fill in all the functions defined for that type. Programmers from object-oriented languages may find the lack of object-oriented classes more bearable. UFCS is implemented in shockingly few languages, and so Puck joins the tiny club that previously consisted of just D, Nim, Koka, and Effekt.</p>
<h2 id="basic-types"><a class="header" href="#basic-types">Basic Types</a></h2>
<pre><code class="language-puck"></code></pre>
<p>Boolean logic and integer operations are standard and as one would expect out of a typed language: <code>and</code>, <code>or</code>, <code>xor</code>, <code>not</code>, <code>shl</code>, <code>shr</code>, <code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>, <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code>, <code>&gt;=</code>, <code>div</code>, <code>mod</code>, <code>rem</code>. Notably:</p>
@@ -376,7 +377,7 @@ print [1].len # 1
<li>integer division is expressed with the keyword <code>div</code> while floating point division uses <code>/</code></li>
<li><code>%</code> is absent and replaced with distinct modulus and remainder operators</li>
<li>boolean operators are bitwise and also apply to integers and floats</li>
-<li>more operators are available via the standard library (<code>exp</code> and <code>log</code>)</li>
+<li>more operators are available via the standard library (<code>std.math.exp</code> and <code>std.math.log</code>)</li>
</ul>
<p>The above operations are performed with <em>operators</em>, special functions that take a prefixed first argument and (often) a suffixed second argument. Custom operators may be implemented, but they must consist of only a combination of the symbols <code>=</code> <code>+</code> <code>-</code> <code>*</code> <code>/</code> <code>&lt;</code> <code>&gt;</code> <code>@</code> <code>$</code> <code>~</code> <code>&amp;</code> <code>%</code> <code>|</code> <code>!</code> <code>?</code> <code>^</code> <code>\</code> for the purpose of keeping the grammar context-free. They are are declared identically to functions.</p>
<p>Term (in)equality is expressed with the <code>==</code> and <code>!=</code> operators. Type equality is expressed with <code>is</code>. Subtyping relations may be queried with <code>of</code>, which has the additional property of introducing new bindings to the current scope in certain contexts (more on this in the <a href="TYPES.html">types document</a>).</p>
@@ -400,9 +401,9 @@ print phrase.last() # ✨
<pre><code class="language-puck">type Result[T] = Result[T, ref Err]
func may_fail: Result[T] = ...
</code></pre>
-<p>Error handling is done via a fusion of functional monadic types and imperative exceptions, with much syntactic sugar. Functions may <code>raise</code> exceptions, but by convention should return <code>Option[T]</code> or <code>Result[T, E]</code> types instead: these may be handled in <code>match</code> or <code>if</code>/<code>of</code> statements. The compiler will track functions that <code>raise</code> errors, and warn on those that are not handled explicitly via <code>try</code>/<code>with</code> statements.</p>
+<p>Error handling is done via a fusion of functional monadic types and imperative exceptions, with much syntactic sugar. Functions may <code>raise</code> exceptions, but by convention should return <code>Option[T]</code> or <code>Result[T, E]</code> types instead: these may be handled in <code>match</code> or <code>if</code>/<code>of</code> statements. The effect system built into the compiler will track functions that <code>raise</code> errors, and warn on those that are not handled explicitly via <code>try</code>/<code>with</code> statements anywhere on the call stack.</p>
<p>A bevy of helper functions and macros are available for <code>Option</code>/<code>Result</code> types, and are documented and available in the <code>std.options</code> and <code>std.results</code> modules (included in the prelude by default). Two in particular are of note: the <code>?</code> macro accesses the inner value of a <code>Result[T, E]</code> or propagates (returns in context) the <code>Error(e)</code>, and the <code>!</code> accesses the inner value of an <code>Option[T]</code> / <code>Result[T, E]</code> or raises an error on <code>None</code> / the specific <code>Error(e)</code>. Both operators take one parameter and so are postfix. The <code>?</code> and <code>!</code> macros are overloaded and additionally function on types as shorthand for <code>Option[T]</code> and <code>Result[T]</code> respectively.</p>
-<p>The utility of the <code>?</code> macro is readily apparent to anyone who has written code in Rust or Swift. The utility of the <code>!</code> function is perhaps less so obvious. These errors raised by <code>!</code>, however, are known to the compiler: and they may be comprehensively caught by a single or sequence of <code>catch</code> statements. This allows for users used to a <code>try</code>/<code>with</code> error handling style to do so with ease, with only the need to add one additional character to a function call.</p>
+<p>The utility of the <code>?</code> macro is readily apparent to anyone who has written code in Rust or Swift. The utility of the <code>!</code> function is perhaps less so obvious. These errors raised by <code>!</code>, however, are known to the compiler: and they may be comprehensively caught by a single or sequence of <code>with</code> statements. This allows for users used to a <code>try</code>/<code>with</code> error handling style to do so with ease, with only the need to add one additional character to a function call.</p>
<p>More details may be found in <a href="ERRORS.html">error handling overview</a>.</p>
<h2 id="blocks-and-loops"><a class="header" href="#blocks-and-loops">Blocks and Loops</a></h2>
<pre><code class="language-puck">loop
@@ -414,12 +415,12 @@ for i in 0 .. 3 do # exclusive
print "{} {}".fmt(i, j)
</code></pre>
<p>Three types of loops are available: <code>for</code> loops, <code>while</code> loops, and infinite loops (<code>loop</code> loops). For loops take a binding (which may be structural, see pattern matching) and an iterable object and will loop until the iterable object is spent. While loops take a condition that is executed upon the beginning of each iteration to determine whether to keep looping. Infinite loops are infinite are infinite are infinite are infinite are infinite are infinite and must be manually broken out of.</p>
-<p>There is no special concept of iterators: iterable objects are any object that implements the <code>Iter[T]</code> class (more on those in <a href="TYPES.html">the type system document</a>), that is, provides a <code>self.next()</code> function returning an <code>Option[T]</code>. As such, iterators are first-class constructs. For loops can be thought of as while loops that unwrap the result of the <code>next()</code> function and end iteration upon a <code>None</code> value. While loops, in turn, can be thought of as infinite loops with an explicit conditional break.</p>
+<p>There is no special concept of iterators: iterable objects are any object that implements the <code>Iter[T]</code> class (more on those in <a href="TYPES.html">the type system document</a>): that is, provides a <code>self.next()</code> function returning an <code>Option[T]</code>. As such, iterators are first-class constructs. For loops can be thought of as while loops that unwrap the result of the <code>next()</code> function and end iteration upon a <code>None</code> value. While loops, in turn, can be thought of as infinite loops with an explicit conditional break.</p>
<p>The <code>break</code> keyword immediately breaks out of the current loop, and the <code>continue</code> keyword immediately jumps to the next iteration of the current loop. Loops may be used in conjunction with blocks for more fine-grained control flow manipulation.</p>
<pre><code class="language-puck">block
statement
-let x = block:
+let x = block
let y = read_input()
transform_input(y)
@@ -437,14 +438,34 @@ block foo
<p>Within a module, functions, types, constants, and other modules may be <em>exported</em> for use by other modules with the <code>pub</code> keyword. All such identifiers are private by default and only accessible module-locally without. Modules are first-class and may be bound, inspected, modified, and returned. As such, imported modules may be <em>re-exported</em> for use by other modules by binding them to a public constant.</p>
<p>More details may be found in the <a href="MODULES.html">modules document</a>.</p>
<h2 id="compile-time-programming"><a class="header" href="#compile-time-programming">Compile-time Programming</a></h2>
-<pre><code class="language-puck"></code></pre>
-<p>Compile-time programming may be done via the previously-mentioned <code>const</code> keyword and <code>when</code> statements: or via <code>const</code> <em>blocks</em>. All code within a <code>const</code> block is evaluated at compile-time and all assignments and allocations made are propagated to the compiled binary as static data. Further compile-time programming may be done via macros: compile-time manipulation of the abstract syntax tree. The macro system is complex, and a description may be found in the <a href="METAPROGRAMMING.html">metaprogramming document</a>.</p>
+<pre><code class="language-puck">## Arbitrary code may execute at compile-time.
+const compile_time =
+ match std.os.platform # known at compile-time
+ of Windows then "windows"
+ of MacOS then "darwin"
+ of Linux then "linux"
+ of Wasi then "wasm"
+ of _ then "unknown platform"
+
+## The propagation operator is a macro so that `return` is injected into the function scope.
+pub macro ?[T](self: Option[T]) =
+ quote
+ match `self`
+ of Some(x) then x
+ of None then return None
+
+## Type annotations and overloading allow us to define syntactic sugar for `Option[T]`, too.
+pub macro ?(T: type) =
+ quote Option[`T`]
+</code></pre>
+<p>Compile-time programming may be done via the previously-mentioned <code>const</code> keyword and <code>when</code> statements: or via macros. Macros operate directly on the abstract syntax tree at compile-time: taking in syntax objects, transforming them, and returning them to be injected. They are <em>hygenic</em> and will not capture identifiers not passed as parameters. While parameters are syntax objects, they can be annotated with types to constrain applications of macros and allow for overloading. Macros are written in ordinary Puck: there is thus no need to learn a separate "macro language", as syntax objects are just standard <code>unions</code>. Additionally, support for <em>quoting</em> removes much of the need to operate on raw syntax objects. A full description may be found in the <a href="METAPROGRAMMING.html">metaprogramming document</a>.</p>
<h2 id="async-system-and-threading"><a class="header" href="#async-system-and-threading">Async System and Threading</a></h2>
<pre><code class="language-puck"></code></pre>
<p>The async system is <em>colourblind</em>: the special <code>async</code> macro will turn any function <em>call</em> returning a <code>T</code> into an asynchronous call returning a <code>Future[T]</code>. The special <code>await</code> function will wait for any <code>Future[T]</code> and return a <code>T</code> (or an error). Async support is included in the standard library in <code>std.async</code> in order to allow for competing implementations. More details may be found in the <a href="ASYNC.html">async document</a>.</p>
<p>Threading support is complex and also regulated to external libraries. OS-provided primitives will likely provide a <code>spawn</code> function, and there will be substantial restrictions for memory safety. I really haven't given much thought to this.</p>
<h2 id="memory-management"><a class="header" href="#memory-management">Memory Management</a></h2>
<pre><code class="language-puck"># Differences in Puck and Rust types in declarations and at call sights.
+# note: this notation is not valid and is for illustrative purposes only
func foo(a:
lent T → &amp;'a T
mut T → &amp;'a mut T
@@ -462,7 +483,7 @@ foo( # this is usually elided
)
</code></pre>
<p>Puck copies Rust-style ownership near verbatim. <code>&amp;T</code> corresponds to <code>lent T</code>, <code>&amp;mut T</code> to <code>mut T</code>, and <code>T</code> to <code>T</code>: with <code>T</code> implicitly convertible to <code>lent T</code> and <code>mut T</code> at call sites. A major goal of Puck is for all lifetimes to be inferred: there is no overt support for lifetime annotations, and it is likely code with strange lifetimes will be rejected before it can be inferred. (Total inference, however, <em>is</em> a goal.)</p>
-<p>Another major difference is the consolidation of <code>Box</code>, <code>Rc</code>, <code>Arc</code>, <code>Cell</code>, <code>RefCell</code> into just two (magic) types: <code>ref</code> and <code>refc</code>. <code>ref</code> takes the role of <code>Box</code>, and <code>refc</code> both the role of <code>Rc</code> and <code>Arc</code>: while <code>Cell</code> and <code>RefCell</code> are disregarded. The underlying motivation for compiler-izing these types is to make deeper compiler optimizations accessible: particularly with <code>refc</code>, where the existing ownership framework is used to eliminate counts. Details on memory safety, references and pointers, and deep optimizations may be found in the <a href="MEMORY_MANAGEMENT.html">memory management overview</a>.</p>
+<p>Another major difference is the consolidation of <code>Box</code>, <code>Rc</code>, <code>Arc</code>, <code>Cell</code>, <code>RefCell</code> into just two (magic) types: <code>ref</code> and <code>refc</code>. <code>ref</code> takes the role of <code>Box</code>, and <code>refc</code> both the role of <code>Rc</code> and <code>Arc</code>: while <code>Cell</code> and <code>RefCell</code> are disregarded. The underlying motivation for compiler-izing these types is to make deeper compiler optimizations accessible: particularly with <code>refc</code>, where the existing ownership framework is used to eliminate unnecessary counts. Details on memory safety, references and pointers, and deep optimizations may be found in the <a href="MEMORY_MANAGEMENT.html">memory management overview</a>.</p>
<h2 id="types-system"><a class="header" href="#types-system">Types System</a></h2>
<pre><code class="language-puck"># The type Foo is defined here as an alias to a list of bytes.
type Foo = list[byte]
@@ -473,14 +494,14 @@ let foo: Foo = [1, 2, 3]
func fancy_dbg(self: Foo) =
print "Foo:"
# iteration is defined for list[byte]
- # so self is implicitly converted from Foo to list[byte]
+ # so it implicitly carries over: and is defined on Foo
for elem in self do
dbg(elem)
# NO implicit conversion to Foo on calls
[4, 5, 6].foo_dbg # this fails!
-Foo([4, 5, 6]).foo_dbg # prints: Foo:\n 4\n\ 5\n 6\n
+Foo([4, 5, 6]).foo_dbg # prints: Foo: 4 5 6
</code></pre>
<p>Finally, a few notes on the type system are in order. Types are declared with the <code>type</code> keyword and are aliases: all functions defined on a type carry over to its alias, though the opposite is not true. Functions defined on the alias <em>must</em> take an object known to be a type of that alias: exceptions are made for type declarations, but at call sites this means that conversion must be explicit.</p>
<pre><code class="language-puck"># We do not want functions defined on list[byte] to carry over,
@@ -500,17 +521,23 @@ pub const MyAlias: type = VeryLongExampleType
<p>If one wishes to define a new type <em>without</em> previous methods accessible, the newtype paradigm is preferred: declaring a single-field <code>struct</code>, and manually implementing functions that carry over. It can also be useful to have <em>transparent</em> type aliases, that is, simply a shorter name to refer to an existing type. These do not require type conversion, implicit or explicit, and can be used freely and interchangeably with their alias. This is done with constants.</p>
<p>Types, like functions, can be <em>generic</em>: declared with "holes" that may be filled in with other types upon usage. A type must have all its holes filled before it can be constructed. The syntax for generics in types much resembles the syntax for generics in functions, and generic <em>constraints</em> and the like also apply.</p>
<h2 id="structs-and-tuples"><a class="header" href="#structs-and-tuples">Structs and Tuples</a></h2>
-<pre><code class="language-puck">type MyStruct = struct
+<pre><code class="language-puck"># standard alternative syntax to inline declarations
+type MyStruct = struct
a: str
b: str
+
+# syntactic sugar for tuple[str, b: str]
type MyTuple = (str, b: str)
let a: MyTuple = ("hello", "world")
print a.1 # world
print a.b # world
+
+let c: MyStruct = {a = a.0, b = a.1}
+print c.b # world
</code></pre>
-<p>Struct and tuple types are declared with <code>struct[&lt;fields&gt;]</code> and <code>tuple[&lt;fields&gt;]</code>, respectively. Their declarations make them look similar at a glance, but they differ fairly fundamentally. Structs are <em>unordered</em>, and every field must be named. They may be constructed with <code>{}</code> brackets. Tuples are <em>ordered</em> and so field names are optional - names are just syntactic sugar for positional access (<code>foo.0</code>, <code>bar.1</code>, ...). Tuples are constructed with <code>()</code> parentheses: and also may be <em>declared</em> with such, as syntactic sugar for <code>tuple[...]</code>.</p>
-<p>It is worth noting that there is no concept of <code>pub</code> at a field level on structs - a type is either fully transparent, or fully opaque. This is because such partial transparency breaks with structural initialization (how could one provide for hidden fields?). However, the <code>@[opaque]</code> attribute allows for expressing that the internal fields of a struct are not to be accessed or initialized: this, however, is only a compiler warning and can be totally suppressed with <code>@[allow(opaque)]</code>.</p>
+<p>Struct and tuple types are declared with <code>struct[&lt;fields&gt;]</code> and <code>tuple[&lt;fields&gt;]</code>, respectively. Their declarations make them look similar at a glance: but they differ fairly fundamentally. Structs are <em>unordered</em> and every field must be named. They may be constructed with brackets. Tuples are <em>ordered</em> and so field names are optional - names are just syntactic sugar for positional access. Tuples are both constructed and optionally <em>declared</em> with parentheses.</p>
+<p>It is worth noting that there is no concept of <code>pub</code> at a field level on structs - a type is either fully transparent, or fully opaque. This is because such partial transparency breaks with structural initialization (how could one provide for hidden fields?). The <code>@[opaque]</code> attribute allows for expressing that the internal fields of a struct are not to be accessed or initialized: this, however, is only a compiler warning and can be totally suppressed with <code>@[allow(opaque)]</code>.</p>
<h2 id="unions-and-enums"><a class="header" href="#unions-and-enums">Unions and Enums</a></h2>
<pre><code class="language-puck">type Expr = union
Literal(int)
@@ -521,7 +548,13 @@ print a.b # world
<p>Union types are composed of a list of <em>variants</em>. Each variant has a <em>tag</em> and an <em>inner type</em> the union wraps over. Before the inner type can be accessed, the tag must be pattern matched upon, in order to handle all possible values. These are also known as <em>sum types</em> or <em>tagged unions</em> in other languages.</p>
<p>Union types are the bread and butter of structural pattern matching. Composed with structs and tuples, unions provide for a very general programming construct commonly referred to as an <em>algebraic data type</em>.
This is often useful as an idiomatic and safer replacement for inheritance.</p>
-<pre><code class="language-puck"></code></pre>
+<pre><code class="language-puck">type Opcode = enum
+ BRK INC POP NIP SWP ROT DUP OVR EQU NEQ GTH LTH JMP JCN JSR STH JCI JMI
+ LDZ STZ LDR STR LDA STA DEI DEO ADD SUB MUL DIV AND ORA EOR SFT JSI LIT
+
+print Opcode.BRK # 0
+...
+</code></pre>
<p>Enum types are similarly composed of a list of <em>variants</em>. These variants, however, are static values: assigned at compile-time, and represented under the hood by a single integer. They function similarly to unions, and can be passed through to functions and pattern matched upon, however their underlying simplicity and default values mean they are much more useful for collecting constants and acting as flags than anything else.</p>
<h2 id="classes"><a class="header" href="#classes">Classes</a></h2>
<pre><code class="language-puck">pub type Iter[T] = class
@@ -534,12 +567,12 @@ pub type Peek[T] = class
</code></pre>
<p>Class types function much as type classes in Haskell or traits in Rust do. They are not concrete types, and cannot be constructed - instead, their utility is via indirection, as parameters in functions or as <code>ref</code> types in structures, providing constraints that some concrete type must meet. They consist of a list of function signatures, implementations of which must exist for the given type passed in in order to compile.</p>
<p>Their major difference, however, is that Puck's classes are <em>implicit</em>: there is no <code>impl</code> block that implementations of their associated functions have to go under. If functions for a concrete type exist satisfying some class, the type implements that class. This does run the risk of accidentally implementing a class one does not desire to, but the author believes such situations are few and far between and well worth the decreased syntactic and semantic complexity. As a result, however, classes are entirely unable to guarantee any invariants hold (like <code>PartialOrd</code> or <code>Ord</code> in Rust do).</p>
-<p>As the compiler makes no such distinction between fields and single-argument functions on a type when determining identifier conflicts, classes similarly make no such distinction. They <em>do</em> distinguish borrowed/mutable/owned parameters, those being part of the type signature.</p>
+<p>As the compiler makes no such distinction between fields and single-argument functions on a type when determining identifier conflicts, classes similarly make no such distinction. Structs may be described with their fields written as methods. They <em>do</em> distinguish borrowed/mutable/owned parameters, those being part of the type signature.</p>
<p>Classes are widely used throughout the standard library to provide general implementations of such conveniences like iteration, debug and display printing, generic error handling, and much more.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="syntax-a-casual-and-formal-look"><a class="header" href="#syntax-a-casual-and-formal-look">Syntax: A Casual and Formal Look</a></h1>
<h2 id="call-syntax"><a class="header" href="#call-syntax">Call Syntax</a></h2>
<p>There is little difference between a function, macro, and operator call. There are only a few forms such calls can take, too, though notably more than most other languages (due to, among other things, uniform function call syntax): hence this section.</p>
-<pre><code># The standard, unambiguous call.
+<pre><code class="language-puck"># The standard, unambiguous call.
routine(1, 2, 3, 4)
# The method call syntax equivalent.
1.routine(2, 3, 4)
@@ -554,7 +587,7 @@ routine
routine 1, 2, 3, 4
</code></pre>
<p>Binary operators have some special rules.</p>
-<pre><code># Valid call syntaxes for binary operators. What can constitute a binary
+<pre><code class="language-puck"># Valid call syntaxes for binary operators. What can constitute a binary
# operator is constrained for parsing's sake. Whitespace is optional.
1 + 2
1+2
@@ -562,12 +595,12 @@ routine 1, 2, 3, 4
+(1, 2)
</code></pre>
<p>As do unary operators.</p>
-<pre><code># The standard call for unary operators. Postfix.
+<pre><code class="language-puck"># The standard call for unary operators. Postfix.
1?
?(1)
</code></pre>
<p>Method call syntax has a number of advantages: notably that it can be <em>chained</em>: acting as a natural pipe operator. Redundant parenthesis can also be omitted.</p>
-<pre><code># The following statements are equivalent:
+<pre><code class="language-puck"># The following statements are equivalent:
foo.bar.baz
foo().bar().baz()
baz(bar(foo))
@@ -615,7 +648,7 @@ of this then ...
of that then ...
</code></pre>
<p>A line beginning with a scope token is treated as attached to the previous expression.</p>
-<pre><code># Technically allowed. Please don't do this.
+<pre><code class="language-puck"># Technically allowed. Please don't do this.
let foo
= ...
@@ -634,7 +667,7 @@ then ...
of that then ...
</code></pre>
<p>This <em>can</em> lead to some ugly possibilities for formatting that are best avoided.</p>
-<pre><code># Much preferred.
+<pre><code class="language-puck"># Much preferred.
let foo =
...
@@ -661,7 +694,7 @@ of that then ...
<p>First, a word on the distinction between <em>expressions</em> and <em>statements</em>. Expressions return a value. Statements do not. That is all.</p>
<p>There are some syntactic constructs unambiguously recognizable as statements: all declarations, modules, and <code>use</code> statements. There are no syntactic constructs unambiguously recognizable as expressions. As calls returning <code>void</code> are treated as statements, and expressions that return a type could possibly return <code>void</code>, there is no explicit distinction between expressions and statements made in the parser: or anywhere before type-checking.</p>
<p>Expressions can go almost anywhere. Our indentation rules above allow for it.</p>
-<pre><code># Some different formulations of valid expressions.
+<pre><code class="language-puck"># Some different formulations of valid expressions.
if cond then
this
@@ -683,7 +716,7 @@ let foo =
else
that
</code></pre>
-<pre><code># Some different formulations of *invalid* expressions.
+<pre><code class="language-puck"># Some different formulations of *invalid* expressions.
# These primarily break the rule that everything following a scope token
# (ex. `=`, `do`, `then`) not at the end of the line must be self-contained.
@@ -741,7 +774,7 @@ of that then ...
<li>logic: <code>not</code> <code>and</code> <code>or</code> <code>xor</code> <code>shl</code> <code>shr</code> <code>div</code> <code>mod</code> <code>rem</code></li>
<li>logic: <code>+</code> <code>-</code> <code>*</code> <code>/</code> <code>&lt;</code> <code>&gt;</code> <code>&lt;=</code> <code>&gt;=</code> <code>==</code> <code>!=</code> <code>is</code></li>
<li>async: <code>async</code> <code>await</code></li>
-<li>types: <code>int</code> <code>uint</code> <code>float</code> <code>i\d+</code> <code>u\d+</code>
+<li>types: <code>int</code> <code>uint</code> <code>float</code> <code>i[\d]+</code> <code>u[\d]+</code>
<ul>
<li><code>f32</code> <code>f64</code> <code>f128</code></li>
<li><code>dec64</code> <code>dec128</code></li>
@@ -845,9 +878,7 @@ Signature ::= Ident Generics? ('(' Type (',' Type)* ')')? (':' Type)?
<h2 id="control-flow"><a class="header" href="#control-flow">Control Flow</a></h2>
<pre><code>If ::= 'if' Expr 'then' Body ('elif' Expr 'then' Body)* ('else' Body)?
When ::= 'when' Expr 'then' Body ('elif' Expr 'then' Body)* ('else' Body)?
-Try ::= 'try' Body
- ('except' Ident ('as' Ident)? (',' Ident ('as' Ident)?)*) 'then' Body)+
- ('finally' Body)?
+Try ::= 'try' Body ('with' Pattern (',' Pattern)* 'then' Body)+ ('finally' Body)?
Match ::= 'match' Expr ('of' Pattern (',' Pattern)* ('where' Expr)? 'then' Body)+
While ::= 'while' Expr 'do' Body
For ::= 'for' Pattern 'in' Expr 'do' Body
@@ -897,7 +928,7 @@ Body ::= (Stmt ';')* Expr
<li><code>int</code>: integer number. x bits of precision by default.
<ul>
<li><code>uint</code>: same as <code>int</code>, but unsigned for more precision.</li>
-<li><code>i[\d+]</code>, <code>u[\d+]</code>: arbitrarily sized integers</li>
+<li><code>i[\d]+</code>, <code>u[\d]+</code>: arbitrarily sized integers</li>
</ul>
</li>
<li><code>float</code>: floating-point number.
@@ -1107,15 +1138,15 @@ func eval(context: mut HashTable[Ident, Value], expr: Expr): Result[Value]
of Variable(ident) then
context.get(ident).err("Variable not in context")
of Application(body, arg) then
- if body of Abstraction(param, body as inner_body):
+ if body of Abstraction(param, body as inner_body) then
context.set(param, context.eval(arg)?) # from std.tables
context.eval(inner_body)
else
Error("Expected Abstraction, found {}".fmt(body))
- of Conditional(condition, then_case, else_case):
+ of Conditional(condition, then_case, else_case) then
if context.eval(condition)? == "true" then
context.eval(then_case)
- else:
+ else
context.eval(else_case)
of expr then
Error("Invalid expression {}".fmt(expr))
@@ -1249,6 +1280,316 @@ use lib.crypto, lib.http
<h2 id="defining-an-external-api"><a class="header" href="#defining-an-external-api">Defining an external API</a></h2>
<p>The filesystem provides an implicit module structure, but it may not be the one you want to expose to users.</p>
<p>...</p>
+<div style="break-before: page; page-break-before: always;"></div><h1 id="memory-management-1"><a class="header" href="#memory-management-1">Memory Management</a></h1>
+<p>Puck's memory management system takes heavy inspiration from Rust.</p>
+<p>The core borrowing semantics are copied 1:1. Puck's <code>lent T</code> is Rust's <code>&amp;T</code>, <code>mut T</code> is <code>&amp;mut T</code>, and <code>T</code> is <code>T</code>. There are several changes, however:</p>
+<ul>
+<li><code>T</code> is coerced into <code>lent T</code> and <code>mut T</code> whenever possible</li>
+<li>all lifetimes are elided: indeterminate lifetimes are disallowed</li>
+<li><code>Copy</code> types are implicit and automatically derived</li>
+<li><code>Box</code> is <code>ref</code> and built-in</li>
+<li><code>Rc</code> and <code>Arc</code> are unified under one compiler-optimized <code>refc</code> type</li>
+</ul>
+<p>I personally think that Rust's system of ownership is <strong>quite good</strong> and a joy to use. And more the matter: I can't think of anything better? Every tweak I try to make to Rust's system falls apart when I work through what it looks like in practice. Making ownership rather than borrowing annotated complicates things, because most of the time ownership <em>is</em> desired. Making references second class simplifies things, but not by all that much, and greatly restricts the expressiveness of the language.</p>
+<p>The exceptions here being coercing to references (which is trivial) and eliding lifetimes. I have seen nothing to suggest total lifetime elision in Rust is impossible: rather, it seems to mostly be an intentional choice for the purpose of explicitness (and not needing to inspect function bodies). I don't like this, and will be attempting to see just how doable total elision is.</p>
+<h2 id="refc-and-ownership-elision"><a class="header" href="#refc-and-ownership-elision"><code>refc</code> and ownership elision</a></h2>
+<p>The <code>refc</code> type is special. Being a compiler built-in, it can take advantage of heavy optimizations: notably the count-eliding Perceus algorithm, used in Nim and Koka. The basic idea is to trace ownership throughout the program graph - and wherever the compiler can statically determine an object is moved, omit counts. And Puck has just the ownership framework to reuse!</p>
+<h2 id="implicit-copy-types"><a class="header" href="#implicit-copy-types">implicit copy types</a></h2>
+<p>There is a distinction between types that can be transparently <em>copied</em> - that is, directly duplicating bytes to form a new object is acceptable - and those not. Pointers to heap memory -</p>
+<p><em>cannot</em> be copied,</p>
+<h2 id="stack-oriented"><a class="header" href="#stack-oriented">stack-oriented</a></h2>
+<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
+</span><span class="boring">fn main() {
+</span>fn foo(a:
+ &amp;T → lent T
+ &amp;mut T → mut T
+ T → T
+):
+ &amp;T → lent T
+ &amp;mut T → mut T
+ T → T
+<span class="boring">}</span></code></pre></pre>
+<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
+</span><span class="boring">fn main() {
+</span>let t: T
+foo(
+ &amp;t → lent t
+ &amp;mut t → mut t
+ t → t
+)
+
+let u =
+ &amp;t → lent t
+ &amp;mut t → mut t
+ t → t
+
+&amp;&amp;t → lent lent t
+*t → deref t
+
+type Foo[T] = struct
+ x: lent T
+<span class="boring">}</span></code></pre></pre>
+<hr />
+<p>I don't know what I want out of a memory management system!</p>
+<p>There are quite a deal of interesting avenues to pursue here, and they all profoundly impact the rest of the language (particularly iteration). In no particular order, here are the ones I've been thinking a lot about.</p>
+<h2 id="reference-counting-with-ownership-elision"><a class="header" href="#reference-counting-with-ownership-elision">reference counting with ownership elision</a></h2>
+<p>Copy Nim/Koka. This is very easy to do: you implement standard reference counting, add a static ownership pass to track places of last use, and remove checks where possible. The benefits of this is that it's simple, deterministic, and performant. It leaves some performance on the table as opposed to Rust, but the usability benefits are high - in particular, you can use it just as you would use any other GC'd language. It's also well-documented by Koka, and lends itself decently well to further optimizations. I will probably end up going with it.</p>
+<h2 id="first-class-references"><a class="header" href="#first-class-references">first-class references</a></h2>
+<p>Copy Rust. This means you need to support stacked borrows and such. That is a bit unfortunate, both syntax and semantics-wise. You inherit all the benefits of Rust-style ownership - and the downsides, too. In particular, you now need to worry about lifetime elision, and whether that's universally possible.</p>
+<p>I am actually really tempted to do this though. Rust + whitespace oriented syntax + better metaprogramming + better modules + overloading + seems fun.</p>
+<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
+</span><span class="boring">fn main() {
+</span>fn foo(a:
+ &amp;T → lent T
+ &amp;mut T → mut T
+ T → T
+):
+ &amp;T → lent T
+ &amp;mut T → mut T
+ T → T
+<span class="boring">}</span></code></pre></pre>
+<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
+</span><span class="boring">fn main() {
+</span>let t: T
+foo(
+ &amp;t → lent t
+ &amp;mut t → mut t
+ t → t
+)
+
+let u =
+ &amp;t → lent t
+ &amp;mut t → mut t
+ t → t
+
+&amp;&amp;t → lent lent t
+*t → deref t
+
+type Foo[T] = struct
+ x: lent T
+<span class="boring">}</span></code></pre></pre>
+<p>o fuk this is really tempting</p>
+<h2 id="second-class-references"><a class="header" href="#second-class-references">second-class references</a></h2>
+<p>Or, simplified borrowing. Much - nigh all - the complexity of ownership in Rust comes from references being <em>first-class</em>: you can return them, you can set aliases to them, you can create them on-the-fly. But what if they weren't first class? What if you couldn't return references? How much expressive power would you lose?</p>
+<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
+</span><span class="boring">fn main() {
+</span>fn foo(a:
+ &amp;T → lent T
+ &amp;mut T → mut T
+ T → T
+):
+ &amp;T → n/a
+ &amp;mut T → n/a
+ T → T
+<span class="boring">}</span></code></pre></pre>
+<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
+</span><span class="boring">fn main() {
+</span>let t: T
+foo(
+ &amp;t → lent t // this could probably be elided
+ &amp;mut t → mut t // this could probably also be elided
+ t → t
+)
+
+let u =
+ &amp;t →
+ &amp;mut t → mut t
+ t → t
+<span class="boring">}</span></code></pre></pre>
+<h2 id="reference-by-default"><a class="header" href="#reference-by-default">reference by default</a></h2>
+<p>...</p>
+<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
+</span><span class="boring">fn main() {
+</span>fn foo(a:
+ &amp;'a T → T
+ &amp;mut T → mut T
+ T → sink T
+):
+ &amp;'a T → lent T
+ &amp;'a mut T → ???
+ T → T
+<span class="boring">}</span></code></pre></pre>
+<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
+</span><span class="boring">fn main() {
+</span>let t: T
+foo(
+ &amp;t → t
+ &amp;mut t → t
+ t → t
+)
+
+let u =
+ &amp;t → lent t
+ &amp;mut t → mut t
+ t → t
+<span class="boring">}</span></code></pre></pre>
+<p>Puck's <code>T</code>: Rust's <code>&amp;T</code>
+Puck's <code>mut T</code>: Rust's <code>&amp;mut T</code>
+Puck's <code>sink T</code> / <code>owned T</code>: Rust's <code>T</code>
+Puck's <code>ref T</code>: ???
+Puck's <code>ptr T</code>: ???</p>
+<p>Types get coerced: no need to say <code>consumes(owned T)</code> (when called).</p>
+<p>Puck's memory management system takes heavy inspiration from Lobster and Nim.
+It is deterministic, requires no reference or lifetime annotations, and has zero runtime overhead (no garbage collector). It achieves this by a sophisticated ownership algorithm building off of ownership implementations in Rust, Nim, Lobster, Koka, and others, but also by <em>restricting</em> the actions of the programmer - in particular, there is no concept of returning a reference from a function or placing a reference within a datatype.</p>
+<p>First, a recap of the problems and perils of memory management:</p>
+<pre><code class="language-c"># Data is explicitly heap-allocated
+type Data = ref struct[x: int, y: int, z: str]
+
+# use-after-free
+
+
+# double-free
+func consumes_ptr(data: mut Data) =
+ let more_data: Data = {x: 5, y: 6, z: "heap allocated!"}
+ data = more_data
+ #
+
+let heap_ptr: Data = malloc(Data.sizeof)
+consumes_ptr(heap_ptr)
+free(heap_ptr) # heap_ptr was already freed! segfault
+</code></pre>
+<p>The above pseudo-code showcases the two fundamental errors that a safe memory management system aims to solve: <strong>double frees</strong> and <strong>use-after-frees</strong>.</p>
+<p><strong>Garbage collected</strong> languages solve this by adding runtime overhead to programs: by injecting a <strong>check</strong> for other existing references before an object would be deallocated, or by periodically <strong>tracing</strong> references to the heap to determine what memory is not live and may be deallocated. But this overhead comes with a problem: there is a class of stubborn programmers who will take minmaxing performance over 70% fewer vulnerabilities any day of the week.</p>
+<p>Thus, Rust's system of ownership - restricting...</p>
+<p>Puck's primary</p>
+<p>The cheapest form of memory management is not having to manage it at all.</p>
+<blockquote>
+<p>The best memory management is.. not to have to manage it at all!</p>
+</blockquote>
+<p>small structs</p>
+<p>You can have references to heap-allocated memory in types, of course - but it's <em>owned</em> by that...</p>
+<p>The whole language is subject to change. But the memory management system, particularly more so - I can sit here and describe how I think it works all day, but I'm inexperienced in the implementation of such systems, and so it's likely I will hit some implementation snags - and although I hope for the best, there is the possibility that some would prove to be fatal.</p>
+<hr />
+<p>should provide for the closure of such systems
+i mean destructors. god</p>
+<p>TL;DR: I think the best form of memory management is reference counting: it is deterministic and can be optimized down.</p>
+<p>TL;DR: Puck uses semi-optimized reference counting that can be manually optimized into an ownership system.</p>
+<p>First, a summary of existing memory systems:</p>
+<ul>
+<li>manual memory management: extremely unsafe!
+<ul>
+<li>extremely unsafe, again! but it can be useful/necessary to turn off safety...</li>
+</ul>
+</li>
+<li>tracing garbage collection
+<ul>
+<li>scans live memory</li>
+<li>typically inconsistent i.e. performance spikes</li>
+</ul>
+</li>
+<li>reference counting
+<ul>
+<li>scans dead memory</li>
+<li>typically slow: querying and updating reference counts is actually a fairly heavy operation</li>
+</ul>
+</li>
+<li>ownership / move semantics
+<ul>
+<li>inflexible: implementing say, linked lists is really hard and has to be done in what amounts to another system</li>
+</ul>
+</li>
+<li>regions?</li>
+</ul>
+<p>Examples of problems:</p>
+<pre><code>x = 5
+y = x
+free(x)
+</code></pre>
+<p>puck's memory management should copy Lobster, and take inspiration from Rust.</p>
+<p>We should be able to copy the same memory management algorithm as Rust, only instead of failing to compile throw an error</p>
+<p>goal: avoid lifetime annotations: https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html</p>
+<p>Access errors: invalid read/write of a pointer
+Buffer overflow - out-of-bound writes can corrupt the content of adjacent objects, or internal data (like bookkeeping information for the heap) or return addresses.
+Buffer over-read - out-of-bound reads can reveal sensitive data or help attackers bypass address space layout randomization.
+Race condition - concurrent reads/writes to shared memory
+Invalid page fault - accessing a pointer outside the virtual memory space. A null pointer dereference will often cause an exception or program termination in most environments, but can cause corruption in operating system kernels or systems without memory protection, or when use of the null pointer involves a large or negative offset.
+Use after free - dereferencing a dangling pointer storing the address of an object that has been deleted.
+Uninitialized variables - a variable that has not been assigned a value is used. It may contain an undesired or, in some languages, a corrupt value.
+Null pointer dereference - dereferencing an invalid pointer or a pointer to memory that has not been allocated
+Wild pointers arise when a pointer is used prior to initialization to some known state. They show the same erratic behavior as dangling pointers, though they are less likely to stay undetected.
+Memory leak - when memory usage is not tracked or is tracked incorrectly
+Stack exhaustion - occurs when a program runs out of stack space, typically because of too deep recursion. A guard page typically halts the program, preventing memory corruption, but functions with large stack frames may bypass the page.
+Heap exhaustion - the program tries to allocate more memory than the amount available. In some languages, this condition must be checked for manually after each allocation.
+Double free - repeated calls to free may prematurely free a new object at the same address. If the exact address has not been reused, other corruption may occur, especially in allocators that use free lists.
+Invalid free - passing an invalid address to free can corrupt the heap.
+Mismatched free - when multiple allocators are in use, attempting to free memory with a deallocation function of a different allocator[20]
+Unwanted aliasing - when the same memory location is allocated and modified twice for unrelated purposes.</p>
+<hr />
+<p>look into: koka/nim (nim i believe is equivalent to koka? but koka has way more documentation)
+rust, of course</p>
+<h1 id="lifetimes"><a class="header" href="#lifetimes">Lifetimes</a></h1>
+<p>no general specification overview, because i don't actually know how thee would work entirely</p>
+<h2 id="notes"><a class="header" href="#notes">notes</a></h2>
+<p><em>you do not need lifetime annotations</em>.
+there <em>is</em> a minimally-correct set of lifetime parameters for any given program:
+rust doesn't do this because it doesn't inspect inside function bodies i guess?</p>
+<p>lifetimes are probably closely tied to move semantics</p>
+<p>so lifetimes come up with things like constants, where everything has a static lifetime</p>
+<p>this seems to me like a bit of a hack and really seems ugly, i'm sure you could do with much better with <code>const</code> blocks</p>
+<p>in fact i simply must use const exprs i love them</p>
+<p>You <em>cannot</em> return a reference from a function.</p>
+<hr />
+<p>The unsafe parts of the language are <code>ptr</code> and <code>unsafe</code>.</p>
+<h2 id="size-of-types"><a class="header" href="#size-of-types">Size of types</a></h2>
+<p>The only types where the size is not known at compile time are:</p>
+<ul>
+<li>interfaces</li>
+</ul>
+<p>types can become re-sized when used with ref</p>
+<h2 id="ownership-and-threads"><a class="header" href="#ownership-and-threads">Ownership and Threads</a></h2>
+<p>The only types that are not Send is Rc. The only types that are not Sync are</p>
+<h2 id="copy-and-clonedeepcopy"><a class="header" href="#copy-and-clonedeepcopy">Copy and Clone/DeepCopy</a></h2>
+<div style="break-before: page; page-break-before: always;"></div><h1 id="metaprogramming"><a class="header" href="#metaprogramming">Metaprogramming</a></h1>
+<p>Puck has rich metaprogramming support, heavily inspired by Nim. Many features that would have to be at the compiler level in most languages (error propagation <code>?</code>, <code>std.fmt.print</code>, <code>?</code>, <code>!</code>, <code>-&gt;</code> type sugar, <code>=&gt;</code> closure sugar, <code>async</code>/<code>await</code>) are instead implemented as macros within the standard library.</p>
+<p>Macros take in fragments of the AST within their scope, transform them with arbitrary compile-time code, and spit back out transformed AST fragments to be injected and checked for validity. This is similar to what the Lisp family of languages do. It has a number of benefits: there is no separate metaprogramming language, it is syntactically and semantically hygienic, and the underlying framework can be reused for all kinds of compile-time code execution.</p>
+<p>By keeping an intentionally minimal AST, some things not possible to express in literal code may be expressible in the AST: in particular, bindings can be injected in many places they could not be injected in ordinarily. (A minimal AST also has the benefit of being quite predictable.)</p>
+<h2 id="scope"><a class="header" href="#scope">Scope</a></h2>
+<p>Macros may not change Puck's syntax: the syntax is flexible enough. They have the same scope as other routines, that is:</p>
+<p><strong>function scope</strong>: takes the arguments within or following a function call</p>
+<pre><code class="language-puck">macro print(params: varargs) =
+ var res = Call("write", [stdout])
+ for param in params do
+ res.params.add(param)
+
+print(1, 2, 3, 4)
+print "hello", " ", "world", "!"
+</code></pre>
+<p><strong>block scope</strong>: takes the expression following a colon as a single argument</p>
+<pre><code class="language-puck">macro my_macro(body)
+
+my_macro
+ 1
+ 2
+ 3
+ 4
+</code></pre>
+<p><strong>operator scope</strong>: takes one or two parameters either as an infix (two parameters) or a postfix (one parameter) operator</p>
+<pre><code class="language-puck"># operators are restricted to punctuation
+macro +=(a, b) =
+ Call("=", [a, Call("+", [a, b])])
+
+a += b
+</code></pre>
+<h2 id="usage"><a class="header" href="#usage">Usage</a></h2>
+<p>Macros typically take a list of parameters <em>without</em> types, but they optionally may be given a type to constrain the usage of a macro. Regardless: as macros operate at compile time, their parameters are not instances of a type, but rather an <code>Expr</code> expression representing a portion of the <em>abstract syntax tree</em>.
+Similarly, macros always return an <code>Expr</code> to be injected into the abstract syntax tree despite the usual absence of an explicit return type, but the return type may be specified to additionally typecheck the returned <code>Expr</code>.</p>
+<pre><code class="language-puck"></code></pre>
+<p>As macros operate at compile time, they may not inspect the <em>values</em> that their parameters evaluate to. However, parameters may be marked <code>const</code>: in which case they will be treated like parameters in functions: as values. (note constant parameters may be written as <code>const[T]</code> or <code>const T</code>.)</p>
+<pre><code class="language-puck">macro ?[T, E](self: Result[T, E]) =
+ quote
+ match `self`
+ of Okay(x) then x
+ of Error(e) then return Error(e)
+
+func meow: Result[bool, ref Err] =
+ let a = stdin.get()?
+</code></pre>
+<h2 id="quoting"><a class="header" href="#quoting">Quoting</a></h2>
+<p>The <code>quote</code> macro is special. It takes in literal code and returns that code <strong>as the AST</strong>. Within quoted data, backticks may be used to break out in order to evaluate and inject arbitrary code: though the code must evaluate to an expression of type <code>Expr</code>. Thus, quoting is <em>structured</em>: one cannot simply quote any arbitrary section. Quoting is very powerful: most macros are implemented using it.</p>
+<pre><code class="language-puck"></code></pre>
+<p>The <code>Expr</code> type is available from <code>std.ast</code>, as are many helpers, and combined they provide the construction of arbitrary syntax trees (indeed, <code>quote</code> relies on and emits types of it). It is a <code>union</code> type with its variants directly corresponding to the variants of the internal AST of Puck.</p>
+<pre><code class="language-puck"></code></pre>
+<p>Construction of macros can be difficult: and so several helpers are provided to ease debugging. The <code>Debug</code> and <code>Display</code> interfaces are implemented for abstract syntax trees: <code>dbg</code> will print a representation of the passed syntax tree as an object, and <code>print</code> will print a best-effort representation as literal code. Together with <code>quote</code> and optionally with <code>const</code>, these can be used to quickly get the representation of arbitrary code.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="error-handling-1"><a class="header" href="#error-handling-1">Error Handling</a></h1>
<p>Puck's error handling is heavily inspired syntactically by Swift and semantically by the underlying effects system. It uses a combination of monadic error handling and effectful error propagation, with much in the way of syntactic sugar for conversion between the two, and leans somewhat heavily on Puck's metaprogramming capabilities. In comparison to Rust, it is considerably more dynamic by default.</p>
<p>There are several ways to handle errors in Puck. If the error is encoded in the type (as an <code>Option</code> or <code>Result</code> type), one can:</p>
@@ -1370,54 +1711,6 @@ In particular, this means there is no concept of an "async function" - any block
<li><a href="https://forum.nim-lang.org/t/7347">Zig-style async/await for Nim</a></li>
</ul>
<p>Is async worth having separate from effect handlers? I think so...</p>
-<div style="break-before: page; page-break-before: always;"></div><h1 id="metaprogramming"><a class="header" href="#metaprogramming">Metaprogramming</a></h1>
-<p>Puck has rich metaprogramming support, heavily inspired by Nim. Many features that would have to be at the compiler level in most languages (error propagation <code>?</code>, <code>std.fmt.print</code>, <code>?</code>, <code>!</code>, <code>-&gt;</code> type sugar, <code>=&gt;</code> closure sugar, <code>async</code>/<code>await</code>) are instead implemented as macros within the standard library.</p>
-<p>Macros take in fragments of the AST within their scope, transform them with arbitrary compile-time code, and spit back out transformed AST fragments to be injected and checked for validity. This is similar to what the Lisp family of languages do. It has a number of benefits: there is no separate metaprogramming language, it is syntactically and semantically hygienic, and the underlying framework can be reused for all kinds of compile-time code execution.</p>
-<p>By keeping an intentionally minimal AST, some things not possible to express in literal code may be expressible in the AST: in particular, bindings can be injected in many places they could not be injected in ordinarily. (A minimal AST also has the benefit of being quite predictable.)</p>
-<p>Macros may not change Puck's syntax: the syntax is flexible enough. They have the same scope as other routines, that is:</p>
-<p><strong>function scope</strong>: takes the arguments within or following a function call</p>
-<pre><code class="language-puck">macro print(params: varargs) =
- var res = Call("write", [stdout])
- for param in params do
- res.params.add(param)
-
-print(1, 2, 3, 4)
-print "hello", " ", "world", "!"
-</code></pre>
-<p><strong>block scope</strong>: takes the expression following a colon as a single argument</p>
-<pre><code class="language-puck">macro my_macro(body)
-
-my_macro
- 1
- 2
- 3
- 4
-</code></pre>
-<p><strong>operator scope</strong>: takes one or two parameters either as an infix (two parameters) or a postfix (one parameter) operator</p>
-<pre><code class="language-puck"># operators are restricted to punctuation
-macro +=(a, b) =
- Call("=", [a, Call("+", [a, b])])
-
-a += b
-</code></pre>
-<p>Macros typically take a list of parameters <em>without</em> types, but they optionally may be given a type to constrain the usage of a macro. Regardless: as macros operate at compile time, their parameters are not instances of a type, but rather an <code>Expr</code> expression representing a portion of the <em>abstract syntax tree</em>.
-Similarly, macros always return an <code>Expr</code> to be injected into the abstract syntax tree despite the usual absence of an explicit return type, but the return type may be specified to additionally typecheck the returned <code>Expr</code>.</p>
-<pre><code class="language-puck"></code></pre>
-<p>As macros operate at compile time, they may not inspect the <em>values</em> that their parameters evaluate to. However, parameters may be marked <code>const</code>: in which case they will be treated like parameters in functions: as values. (note constant parameters may be written as <code>const[T]</code> or <code>const T</code>.)</p>
-<pre><code class="language-puck">macro ?[T, E](self: Result[T, E]) =
- quote
- match `self`
- of Okay(x) then x
- of Error(e) then return Error(e)
-
-func meow: Result[bool, ref Err] =
- let a = stdin.get()?
-</code></pre>
-<p>The <code>quote</code> macro is special. It takes in literal code and returns that code <strong>as the AST</strong>. Within quoted data, backticks may be used to break out in order to evaluate and inject arbitrary code: though the code must evaluate to an expression of type <code>Expr</code>. Thus, quoting is <em>structured</em>: one cannot simply quote any arbitrary section. Quoting is very powerful: most macros are implemented using it.</p>
-<pre><code class="language-puck"></code></pre>
-<p>The <code>Expr</code> type is available from <code>std.ast</code>, as are many helpers, and combined they provide the construction of arbitrary syntax trees (indeed, <code>quote</code> relies on and emits types of it). It is a <code>union</code> type with its variants directly corresponding to the variants of the internal AST of Puck.</p>
-<pre><code class="language-puck"></code></pre>
-<p>Construction of macros can be difficult: and so several helpers are provided to ease debugging. The <code>Debug</code> and <code>Display</code> interfaces are implemented for abstract syntax trees: <code>dbg</code> will print a representation of the passed syntax tree as an object, and <code>print</code> will print a best-effort representation as literal code. Together with <code>quote</code> and optionally with <code>const</code>, these can be used to quickly get the representation of arbitrary code.</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="interop-with-other-languages"><a class="header" href="#interop-with-other-languages">Interop with Other Languages</a></h1>
<blockquote>
<p>! This section is a <strong>draft</strong>. Many important details have yet to be ironed out.</p>
@@ -1461,6 +1754,749 @@ pub func this_function() = ...
<li><a href="https://github.com/PMunch/futhark">Futhark</a></li>
<li><a href="https://lib.haxe.org/p/callfunc/">Haxe's <code>callfunc</code></a></li>
</ul>
+<div style="break-before: page; page-break-before: always;"></div><h1 id="example-programs"><a class="header" href="#example-programs">Example Programs</a></h1>
+<p>These are taken directly from the (work-in-progress) stdlib.</p>
+<h2 id="stdoptions"><a class="header" href="#stdoptions">std.options</a></h2>
+<pre><code class="language-puck">## std.options: Optional types.
+## This module is imported by default.
+
+use std.format
+
+## The `Option` type.
+## A type that represents either the presence or absence of a value.
+pub type Option[T] = union
+ Some(T)
+ None
+
+## Syntactic sugar for optional type declarations.
+pub macro ?(T: type) =
+ quote Option[`T`]
+
+## Directly accesses the inner value. Throws an exception if None.
+pub func ![T](self: T?): T =
+ if self of Some(x) then x
+ else raise "empty"
+
+## Indirect access. Propagates `None`.
+pub macro ?[T](self: Option[T]) =
+ quote
+ match `self`
+ of Some(x) then x
+ of None then return None
+
+## Checks if a type is present within an `Option` type.
+pub func is_some[T](self: T?): bool =
+ self of Some(_)
+## Checks if a type is not present within an `Option` type.
+pub func is_none[T](self: T?): bool =
+ self of None
+
+## Converts an `Option[T]` to a `Result[T, E]` given a user-provided error.
+pub func err[T, E](self: T?, error: E): Result[T, E] =
+ if self of Some(x) then
+ Okay(x)
+ else
+ Error(error)
+
+## Applies a function to `T`, if it exists.
+pub func map[T, U](self: T?, fn: T -&gt; U): U? =
+ if self of Some(x) then
+ Some(fn(x))
+ else
+ None
+## Converts `T` to a `None`, if `fn` returns false and it exists.
+pub func filter[T](self: T?, fn: T -&gt; bool): T? =
+ if self of Some(x) and fn(x) then
+ Some(x)
+ else
+ None
+
+## Applies a function to T, if it exists. Equivalent to `self.map(fn).flatten`.
+pub func flatmap[T, U](self: T?, fn: T -&gt; U?): U? =
+ if self of Some(x) then
+ fn(x)
+ else
+ None
+## Converts from Option[Option[T]] to Option[T].
+pub func flatten[T](self: T??): T? =
+ if self of Some(Some(x)) then
+ Some(x)
+ else
+ None
+
+## Returns the inner value or a default.
+pub func get_or[T](self: T?, default: T): T =
+ if self of Some(x) then x
+ else default
+
+## Overloads the `==` operation for use on Options.
+pub func ==[T](a, b: T?): bool =
+ if (a, b) of (Some(x), Some(y)) then
+ x == y
+ else
+ false
+
+## Overloads the `str()` function for use on Options.
+pub func str[T: Display](self: T?): str =
+ if self of Some(x) then
+ "Some({})".fmt(x.str)
+ else
+ "None"
+
+# references:
+# https://nim-lang.github.io/Nim/options.html
+# https://doc.rust-lang.org/std/option/enum.Option.html
+</code></pre>
+<h2 id="stdresults"><a class="header" href="#stdresults">std.results</a></h2>
+<pre><code class="language-puck">## std.results: Result types.
+## This module is imported by default.
+
+use std.[options, format]
+
+## The Result type. Represents either success or failure.
+pub type Result[T, E] = union
+ Okay(T)
+ Error(E)
+
+## The Err class. Useful for dynamically dispatching errors.
+pub type Err = class
+ str(Self): str
+ dbg(Self): str
+
+## A `Result` type that uses dynamically dispatched errors.
+## The `Error` may be any type implementing `Err`.
+pub type Result[T] = Result[T, ref Err]
+## A `Result` type that only checks for success.
+## Does not contain a value.
+# pub type Success[E] = Result[void, E]
+## A `Result` type that only checks for success.
+## Does not contain a value. Dynamically dispatched.
+# pub type Success = Result[void]
+
+## Syntactic sugar for dynamic result type declarations.
+pub macro !(T: type) =
+ quote Result[`T`]
+
+## Indirect access. Propagates `Error`.
+pub macro ?[T, E](self: Result[T, E]) =
+ quote
+ match `self`
+ of Okay(x) then x
+ of Error(e) then return Error(e)
+
+## Checks if a `Result` type was successful.
+pub func is_ok[T, E](self: Result[T, E]): bool =
+ self of Okay(_)
+## Checks if a `Result` type was not successful.
+pub func is_err[T, E](self: Result[T, E]): bool =
+ self of Error(_)
+
+## Converts from a `Result[T, E]` to an `Option[T]`.
+pub func ok[T, E](self: Result[T, E]): T? =
+ if self of Okay(x) then
+ Some(x)
+ else
+ None
+## Converts from a `Result[T, E]` to an `Option[E]`.
+pub func err[T, E](self: Result[T, E]): E? =
+ if self of Error(x) then
+ Some(x)
+ else
+ None
+
+## Applies a function to `T`, if self is `Okay`.
+pub func map[T, E, U](self: Result[T, E], fn: T -&gt; U): Result[U, E] =
+ match self
+ of Okay(x) then
+ Okay(fn(x))
+ of Error(e) then
+ Error(e)
+## Applies a function to `E`, if self is `Error`.
+pub func map_err[T, E, F](self: Result[T, E], fn: E -&gt; F): Result[T, F] =
+ match self
+ of Error(e) then
+ Error(fn(e))
+ of Okay(x) then
+ Okay(x)
+
+## Applies a function to `T`, if it exists. Equivalent to `self.map(fn).flatten`.
+pub func flatmap[T, E, U](self: Result[T, E], fn: T -&gt; Result[U, E]): Result[U, E] =
+ match self
+ of Okay(x) then
+ fn(x)
+ of Error(e) then
+ Error(e)
+## Converts from a `Result[Result[T, E], E]` to a `Result[T, E]`.
+pub func flatten[T, E](self: Result[Result[T, E], E]): Result[T, E] =
+ match self
+ of Okay(Okay(x)) then
+ Okay(x)
+ of Okay(Error(e)), Error(e) then
+ Error(e)
+
+## Transposes a `Result[Option[T], E]` to an `Option[Result[T, E]]`.
+pub func transpose[T, E](self: Result[T?, E]): Result[T, E]? =
+ match self
+ of Okay(Some(x)) then
+ Some(Okay(x))
+ of Okay(None), Error(_) then
+ None
+## Transposes an `Option[Result[T, E]]` to a `Result[Option[T], E]`. Takes a default error.
+pub func transpose[T, E](self: Result[T, E]?, error: E): Result[T?, E] =
+ match self
+ of Some(Okay(x)) then Okay(Some(x))
+ of Some(Error(e)) then Error(e)
+ of None then Error(error)
+
+## Returns the inner value or a default.
+pub func get_or[T, E](self: Result[T, E], default: T): T =
+ if self of Okay(x) then x
+ else default
+
+## Directly accesses the inner value. Throws an exception if `Error`.
+pub func ![T, E](self: Result[T, E]): T =
+ match self
+ of Okay(x) then x
+ of Error(e) then raise e
+## Directly accesses the inner error. Throws an exception of type T if `Okay`.
+pub func get_err[T, E](self: Result[T, E]): E =
+ match self
+ of Error(e) then e
+ of Okay(x) then raise x
+
+## Overloads the `==` operation for use on Results.
+pub func ==[T, E, F](a: Result[T, E], b: Result[T, F]): bool =
+ if (a, b) of (Okay(x), Okay(y)) then
+ x == y
+ else
+ false
+
+## Overloads the `str()` function for use on Results.
+pub func str[T: Display, E: Display](self: Result[T, E]): str =
+ match self
+ of Some(x) then
+ "Okay({})".fmt(x.str)
+ of Error(e) then
+ "Error({})".fmt(e.str)
+
+# references:
+# https://doc.rust-lang.org/std/result/enum.Result.html
+# https://github.com/arnetheduck/nim-results
+# https://github.com/codex-storage/questionable
+</code></pre>
+<h2 id="stdformat"><a class="header" href="#stdformat">std.format</a></h2>
+<pre><code class="language-puck">## std.format: Niceties around printing and debugging.
+## This module is imported by default.
+
+## The Display class. Any type implementing `str` is printable.
+## Any type that is Display must necessarily also implement Debug.
+pub type Display = class
+ str(Self): str
+ dbg(Self): str
+
+## The Debug class. Broadly implemented for every type with compiler magic.
+## Types can (and should) override the generic implementations.
+pub type Debug = class
+ dbg(Self): str
+
+## Prints all of its arguments to the command line.
+pub func print(params: varargs[Display]) =
+ stdout.write(params.map(x =&gt; x.str).join(" "), "\n")
+
+## Prints all of its arguments to the command line, in Debug form.
+##
+## Note: this function is special! It does not count as a side effect.
+## This breaks effect tracking, of course: but `dbg` is for debugging.
+## It will produce a warning in code compiled for release.
+@[pure]
+pub func dbg(params: varargs[Debug]) =
+ stdout.write(params.map(x =&gt; x.dbg).join(" "), "\n")
+
+## A dummy implementation of the Display class for strings.
+pub func str(self: str): str = self
+## An implementation of the Debug class for strings.
+pub func dbg(self: str): str = "\"" &amp; self &amp; "\""
+
+## An implementation of the Debug class for all structs.
+## Uses the special `struct` typeclass.
+pub func dbg[T: Debug](self: struct[T]): str =
+ "{{}}".fmt(self.fields.map((key, val) =&gt; key &amp; ":" &amp; val.dbg))
+
+## An implementation of the Debug class for all tuples.
+## Uses the special `tuple` typeclass.
+pub func dbg[T: Debug](self: tuple[T]): str =
+ "({})".fmt(self.fields.map((key, val) =&gt;
+ key.map(x =&gt; x &amp; ":").get_or("") &amp; val.dbg).join(", "))
+
+## An implementation of the Debug class for all arrays and lists.
+pub func dbg[T: Debug](self: Iter[T]): str =
+ "[{}]".fmt(self.map(x =&gt; x.dbg).join(", "))
+
+## The fmt macro. Builds a formatted string from its arguments.
+pub macro fmt(self: const str, args: varargs[Display]): str =
+ let parts = self.split("{}")
+ if parts.len != args.len + 1 then
+ macro_error("wrong number of arguments")
+ use std.ast
+ var res = parts.get(0)!
+ for i, arg in args do
+ res &amp;= quote(`parts` &amp; str(`arg`) &amp;) # fixme
+ res &amp;= parts.last()!
+ res
+</code></pre>
+<h2 id="stddebug"><a class="header" href="#stddebug">std.debug</a></h2>
+<pre><code class="language-puck">## std.debug: Useful functions for debugging.
+## This module is imported by default.
+
+## The `assert` macro checks that a provided assertation is true,
+## and panics and dumps information if it is not.
+## Asserts remain in release builds. If not desired, see `dbg_assert`
+pub macro assert(cond: bool) =
+ quote
+ if not `cond` then
+ panic "assertation failed!\n {}".fmt(dbg(`cond`))
+
+## The `dbg_assert` function provides an assert that is compiled out in release builds.
+## This is useful for debugging performance-critical code.
+pub macro dbg_assert(cond: bool) =
+ quote
+ when debug then # fixme: where is this constant coming from?
+ assert `cond`
+
+## The `discard` function consumes an object of any type.
+## Useful for throwing away the result of a computation.
+pub func discard[T](self: T) =
+ return
+
+## The `panic` function prints a message to `stderr` and quits.
+pub func panic(message: str): never =
+ stderr.write(message, "\n")
+ std.os.exit(1)
+
+## The special ... syntax is used to mark unimplemented parts of code.
+## Such code will compile, but panic upon being called at runtime.
+## It is usable almost anywhere, including in type declarations, thanks to compiler magic.
+@[magic]
+pub func ...: never =
+ panic("unimplemented")
+</code></pre>
+<h2 id="stdlists"><a class="header" href="#stdlists">std.lists</a></h2>
+<pre><code class="language-puck">## std.lists: Dynamic arrays.
+## This module is imported by default.
+
+## The fundamental list type. Heap-allocated.
+## Equivalent to Vec&lt;T&gt; in other languages.
+@[opaque] # opaque on a struct tells us raw field access breaks invariants.
+pub type list[T] = struct
+ data: ptr T
+ capacity: uint
+ length: uint
+
+## A transparent, common alias for a list of bytes.
+pub type bytes = list[byte]
+
+## Initialize and return an empty list with inner type T.
+pub func init[T]: list[T] =
+ { data = nil, capacity = 0, length = 0 } # fixme: nil!!!!!
+
+## Gets the length of a list.
+@[inline] # idk what to do with attributes
+pub func len[T](self: lent list[T]): uint =
+ self.length
+
+pub func empty[T](self: lent list[T]): bool =
+ self.length == 0
+
+## Gets the internal capacity of a list.
+func cap[T](self: lent list[T]): uint =
+ self.capacity
+
+## Expands the capacity of a list.
+@[safe]
+func grow[T](self: mut list[T]) =
+ self.capacity = max(self.length + 1, self.capacity * 2)
+ self.data = self.data.realloc(self.capacity * sizeof(T))
+
+## Pushes a new element to the end of a list.
+@[safe]
+pub func push[T](self: mut list[T], val: T) =
+ if self.capacity == self.length then self.grow()
+ self.data.set(val, offset = self.length)
+ self.length += 1
+
+## Takes ownership of and pushes all the values of a list into another list.
+pub func push[T](self: mut list[T], values: list[T]) =
+ for val in values do
+ self.push(val)
+
+## Removes &amp; returns an element from the end of a list, if it exists.
+@[safe]
+pub func pop[T](self: mut list[T]): T? =
+ if self.length == 0 then
+ None
+ else
+ self.length -= 1
+ Some(self.data.get(offset = self.length))
+
+## Returns a reference to an element of a list, if in range.
+@[safe]
+pub func get[T](self: lent list[T], i: uint): lent T? =
+ if i &gt; self.length then
+ None
+ else # fixme: interior mutability
+ Some(lent self.data.get(offset = i))
+## Returns a mutable reference to an element of a list, if in range.
+@[safe]
+pub func get[T](self: mut list[T], i: uint): mut T? =
+ if i &gt; self.length then
+ None
+ else # fixme: interior mutability
+ Some(mut self.data.get(offset = i))
+
+## Sets the element of a list to a value.
+@[safe]
+pub func set[T](self: mut list[T], i: uint, val: T) =
+ assert i &lt;= self.length, "index out of bounds"
+ Okay(self.data.set(offset = i, val))
+
+## Inserts a value at a location and shifts elements of the list accordingly.
+@[safe]
+pub func insert[T](self: mut list[T], i: uint, val: T) =
+ assert i &lt;= self.length, "index out of bounds"
+ if self.capacity == self.length then self.grow()
+ self.data.offset(i).copy(self.data.offset(i + 1), self.length - i)
+ self.data.set(i, val)
+ self.length += 1
+## Inserts a list of values at a location and shifts elements of the list accordingly.
+pub func insert[T](self: mut list[T], i: uint, vals: list[T]) =
+ for val in vals.rev: # inserting backwards avoids counting
+ self.insert(val, i)
+
+## Removes a value at a location and shifts elements of the list accordingly.
+@[safe]
+pub func remove[T](self: mut list[T], i: uint): T? =
+ if index &lt; self.length then None
+ else
+ self.length -= 1
+ let res = self.data.get(i)
+ self.data.offset(i + 1).copy(self.data.offset(i), self.length - i)
+ res
+
+## Gets the last element of a list, if it exists.
+pub func last[T](self: lent list[T]): lent T? =
+ self.get(self.len - 1)
+## Gets the last element of a list mutably, if it exists.
+pub func last[T](self: mut list[T]): mut T? =
+ self.get(self.len - 1)
+
+# reference: https://doc.rust-lang.org/nomicon/vec/vec.html
+</code></pre>
+<h2 id="stdstrings"><a class="header" href="#stdstrings">std.strings</a></h2>
+<pre><code class="language-puck">## std.strings: The standard implementation of strings.
+## This module is imported by default.
+
+## A primitive string type.
+##
+## We do not want methods defined on `list[byte]` to carry over,
+## so we define `str` as a newtype.
+@[opaque]
+pub type str = struct
+ data: list[byte]
+
+## Initialize and return an empty string.
+pub func init: str = { data = [] }
+
+## Gets the length of a string.
+## This is an O(n) operation, due to UTF-8 encoding.
+pub func len(self: lent str): uint =
+ var res: uint
+ for _ in self do
+ res += 1
+ res
+
+## Pushes a character to the end of a mutable string.
+pub func push(self: mut str, val: char) =
+ self.data.push(val.byte) # todo: obsolete by from/to conversion??
+
+## Pushes an owned string to the end of a mutable string.
+pub func push(self: mut str, val: str) =
+ self.data.push(val.bytes) # todo: obsolete by from/to conversion??
+
+## Removes and returns the last character of a string, if it exists.
+##
+## SAFETY: We return early upon an empty string.
+## And decrement by one char for a non-empty string.
+@[safe]
+pub func pop(self: mut str): char? =
+ let char = self.chars.rev.next?
+ self.data.set_len(self.len - char.len) # this is normally unsafe.
+ Some(char)
+
+## Returns the character at the provided index, if it exists.
+pub func get(self: str, i: uint): char? =
+ ...
+
+## Sets the character at the provided index, if it exists.
+## As strings are packed, this may call str.grow and reallocate.
+## oh fuck we have to insert + remove anyway
+pub func set(self: mut str, i: uint, val: char) =
+ ...
+
+## Inserts a character at an arbitrary position within a string.
+## Panics on failure. (todo: can we do better?)
+pub func insert(self: mut str, i: uint, val: char) =
+ ...
+
+## Removes and returns a character at an arbitrary position within a string.
+## Panics on failure. (todo: can we do better?)
+pub func remove(self: mut str, i: uint): char? =
+ ...
+
+## Syntactic sugar for string appending.
+pub func &amp;=(a: mut str, b: str) =
+ a.push(b)
+
+## The concatenation operator. Consumes two strings.
+pub func &amp;(a: str, b: str): str =
+ a.push(b)
+ a
+
+## Conversion from a string to a list of bytes. Zero-cost.
+pub func to(self: str): list[byte] = self.data
+## Conversion from a str to a list[char]. Reallocates.
+pub func to(self: str): list[char] =
+ var res: list[char]
+ for char in self do res.push(char)
+ res
+## Conversion from a char to an array of bytes. Zero-cost.
+@[safe] # possibly unsafe?? depends on repr of arrays
+pub func to(self: char): array[byte, 4] =
+ self.cast[array[byte, 4]]
+
+# reference: https://doc.rust-lang.org/std/string/struct.String.html
+</code></pre>
+<h2 id="stdcompare"><a class="header" href="#stdcompare">std.compare</a></h2>
+<pre><code class="language-puck">## std.compare: Classes for comparable types.
+
+## The Eq class. For types with some notion of equivalence.
+pub type Eq = class
+ ==(Self, Self): bool
+
+## A blanket implementation of a corresponding not-equal function.
+pub !=[T: Eq](a: T, b: T): bool =
+ not(a == b)
+
+## The Compare class. For a type comparable with itself.
+pub type Compare = class
+ &lt;(a: Self, b: Self): bool
+
+## A blanket implementation of a corresponding greater-than function.
+## Note to self: do NOT inline!
+pub func &gt;[T: Compare](a: T, b: T): bool =
+ b &lt; a
+
+## The Ord class. For types with some notion of equivalence and comparision.
+##
+## Note: This is *not* a mathematical notion of an order!
+## No invariants on `&lt;` nor `==` are guaranteed to hold, as classes
+## are implicitly implementable.
+pub type Ord = class
+ &lt;(a: Self, b: Self): bool
+ ==(a: Self, b: Self): bool
+
+## A blanket implementation of a corresponding less-than-or-equal function.
+pub func &lt;=[T: Ord](a: T, b: T): bool =
+ a &lt; b or a == b
+
+## A blanket implementation of a corresponding greater-than-or-equal function.
+pub func &gt;=[T: Ord](a: T, b: T): bool =
+ a &gt; b or a == b
+
+# reference: https://doc.rust-lang.org/std/cmp
+</code></pre>
+<h2 id="stdconvert"><a class="header" href="#stdconvert">std.convert</a></h2>
+<pre><code class="language-puck">## std.convert: Classes for type coersion and conversion.
+## This module is imported by default.
+
+## The Coerce class is used for type conversion that will not fail.
+## Its associated methods, `from` and `into`, are used internally
+## by the compiler for implicit type conversion (coersion).
+pub type Coerce[T] = class
+ to(Self): T
+ # from(T): Self
+
+## The `from` function is automatically implemented for all types that
+## implement `to`: that is, all types T that are convertable to U.
+pub func from[T: Coerce[U], U](self: U): T =
+ to(self)
+
+## The Convert class is used for type conversion that may fail.
+# We'll see what this breaks.
+pub type Convert[T, E] = class
+ to(Self): Result[T, E]
+</code></pre>
+<h2 id="stdranges"><a class="header" href="#stdranges">std.ranges</a></h2>
+<pre><code class="language-puck">## std.ranges: Ranges of integers and other things. For iteration.
+## This module is imported by default.
+
+type Range[T] = struct
+ start: T
+ end: T
+
+type RangeIncl[T] = struct
+ start: T
+ end: T
+ done: bool
+
+## Exclusive ranges. Useful for iteration.
+## Includes `from`, does not include `to`.
+pub func ..(from: int, to: int): Range[int] = { from, to }
+
+## Inclusive ranges. Useful for ranges.
+## Includes `from` and `to`.
+pub func ..=(from: int, to: int): RangeIncl[int] = { from, to, done = false }
+
+# todo: implement for all types that can increment or smth idk
+pub func next[T: int](self: mut Range[T]): T? =
+ if self.start &lt; self.end then
+ self.start += 1
+ Some(self.start - 1)
+ else
+ None
+
+# todo: We don't need a mutable Range here to peek.
+# How does this interact with classes?
+pub func peek[T: int](self: mut Range[T]): T? =
+ self.peek_nth(0)
+
+pub func peek_nth[T: int](self: mut Range[T], i: uint): T? =
+ let res = self.start + i
+ if res &lt; self.end then
+ Some(res)
+ else
+ None
+
+pub func next[T: int](self: mut RangeIncl[T]): T? =
+ if self.done then
+ None
+ elif self.start &lt; self.end then
+ let res = self.start
+ self.start += 1
+ Some(res)
+ elif self.start == self.end then
+ self.done = true
+ Some(self.start)
+ else
+ self.done = true
+ None
+
+pub func peek[T: int](self: mut RangeIncl[T]): T? =
+ self.peek_nth(0)
+
+pub func peek_nth[T: int](self: mut RangeIncl[T], i: uint): T? =
+ let res = self.start + i
+ if res &lt;= self.end
+ then Some(res)
+ else None
+</code></pre>
+<h2 id="stdast"><a class="header" href="#stdast">std.ast</a></h2>
+<pre><code class="language-puck">## std.ast: Exposes the AST for building and operating on with macros.
+
+## The `Expr` type represents the abstract syntax tree of Puck itself.
+## It notably lacks type information. It is also not necessarily syntactically
+## correct-by-construction: Cond, Try, and Match expressions must have at least
+## one branch in their branches (yet this is not expressible here).
+pub type Expr = union
+ # Terms
+ Ident(str)
+ Number(int)
+ Float(float)
+ Char(char)
+ String(str)
+ Struct(list[(field: str, value: Expr)]) # {...}
+ Tuple(list[(field: str?, value: Expr)]) # (...)
+ List(list[Expr]) # [...]
+ # Bindings
+ Let(id: Pattern, kind: Type?, value: ref Expr)
+ Var(id: Pattern, kind: Type?, value: ref[Expr]?)
+ Constant(public: bool, id: Pattern, kind: Type?, value: ref Expr)
+ FuncDecl(
+ public: bool,
+ id: str,
+ generics: list[(id: str, kind: Type?)],
+ params: list[(id: str, kind: Type)],
+ kind: Type?,
+ body: list[Expr])
+ MacroDecl(
+ public: bool,
+ id: str,
+ generics: list[(id: str, kind: Type?)],
+ params: list[(id: str, kind: Type?)],
+ kind: Type?,
+ body: list[Expr])
+ TypeDecl(
+ public: bool,
+ id: str,
+ generics: list[str],
+ body: Type)
+ Module(
+ public: bool,
+ id: str,
+ generics: list[str], # always empty for now
+ body: list[Expr])
+ Use(modules: list[(path: str, alias: str?)])
+ # Control Flow
+ Call(id: str, params: list[Expr])
+ Cond(
+ branches: list[(cond: Expr, body: list[Expr])],
+ else_body: list[Expr])
+ Try(
+ try_body: list[Expr],
+ catches: list[(exceptions: list[str], body: list[Expr])],
+ finally_body: list[Expr]) # todo: throw this out
+ Match(
+ item: ref Expr,
+ branches: list[(pattern: Pattern, guard: Expr?, body: list[Expr])])
+ Block(id: str?, body: list[Expr])
+ Static(body: list[Expr])
+ For(binding: Pattern, range: ref Expr, body: list[Expr])
+ While(cond: ref Expr, body: list[Expr])
+ Loop(body: list[Expr])
+ Attribute(on: ref Expr)
+ Quote(body: ref Expr)
+ Unquote(body: ref Expr)
+
+pub type Type = ref union
+ Never
+ Int(size: uint)
+ Dec(size: uint)
+ Float(size: uint)
+ Func(from: list[Type], to: Type)
+ Struct(list[(id: str, kind: Type)])
+ Tuple(list[(id: str?, kind: Type)])
+ Union(list[(id: str, kind: Type)])
+ Class(list[(id: str, from: list[Type], to: Type?)])
+ Array(size: uint, kind: Type)
+ List(Type)
+ Slice(Type) # todo: plus ownership
+ Alias(str) # todo: params?? huh?
+ Const(Type)
+ Lent(Type)
+ Mut(Type)
+ Ref(Type)
+ Refc(Type)
+ Ptr(Type)
+
+pub type Pattern = union
+ Ident(str)
+ Number(int), Float(float), Char(char), String(str)
+ Struct(name: str, params: list[Pattern])
+ Tuple(list[Pattern])
+ List(list[Pattern])
+
+@[magic]
+pub func quote(body): Expr
+</code></pre>
</main>