diff options
Diffstat (limited to 'docs/book/METAPROGRAMMING.html')
-rw-r--r-- | docs/book/METAPROGRAMMING.html | 57 |
1 files changed, 21 insertions, 36 deletions
diff --git a/docs/book/METAPROGRAMMING.html b/docs/book/METAPROGRAMMING.html index 92bb4cb..2bb9333 100644 --- a/docs/book/METAPROGRAMMING.html +++ b/docs/book/METAPROGRAMMING.html @@ -7,7 +7,7 @@ <!-- Custom HTML head --> - + <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="theme-color" content="#ffffff"> @@ -174,52 +174,53 @@ <div id="content" class="content"> <main> <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>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 Nim and the Lisp family of languages do. -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. Code is syntactically checked (parsed), but <em>not</em> semantically checked (typechecked) before being passed to macros. This may change in the future<!-- (to require arguments to be semantically correct)-->. Macros have the same scope as other routines, that is:</p> +<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>-></code> type sugar, <code>=></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) = - for param in params: - result.add(quote(stdout.write(`params`.str))) + var res = Call("write", [stdout]) + for param in params do + res.params.add(param) print(1, 2, 3, 4) -print "hello", " ", "world", "!" +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: +my_macro 1 2 3 4 </code></pre> -<p><strong>operator scope</strong>: takes one or two parameters either as a postfix (one parameter) or an infix (two parameters) operator</p> -<pre><code class="language-puck">macro +=(a, b) = - quote: - `a` = `a` + `b` +<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 with <code>static[T]</code>: in which case they will be treated like parameters in functions: as values. (note static parameters may be written as <code>static[T]</code> or <code>static T</code>.) There are many restrictions on what might be <code>static</code> parameters. Currently, it is constrained to literals i.e. <code>1</code>, <code>"hello"</code>, etc, though this will hopefully be expanded to any function that may be evaluated statically in the future.</p> +<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): x - of Error(e): return Error(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>. <!-- Variables (of type `Expr`) may be *injected* into the literal code by wrapping them in backticks. This reuse of backticks does mean that defining new operators is impossible within quoted code. --></p> +<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>static</code>, these can be used to quickly get the representation of arbitrary code.</p> +<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> </main> @@ -250,22 +251,6 @@ func meow: Result[bool, ref Err] = </div> - <!-- Livereload script (if served using the cli tool) --> - <script> - const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:'; - const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload"; - const socket = new WebSocket(wsAddress); - socket.onmessage = function (event) { - if (event.data === "reload") { - socket.close(); - location.reload(); - } - }; - - window.onbeforeunload = function() { - socket.close(); - } - </script> |