From 4a2c54ef3cbf1baaf1e76be620c57ef7a9723b45 Mon Sep 17 00:00:00 2001
From: Robert Borghese <28355157+RobertBorghese@users.noreply.github.com>
Date: Thu, 5 Jan 2023 19:36:48 -0500
Subject: [PATCH 1/2] Create 0000-trailing-block-expr.md
---
proposals/0000-trailing-block-expr.md | 152 ++++++++++++++++++++++++++
1 file changed, 152 insertions(+)
create mode 100644 proposals/0000-trailing-block-expr.md
diff --git a/proposals/0000-trailing-block-expr.md b/proposals/0000-trailing-block-expr.md
new file mode 100644
index 0000000..1a85ca2
--- /dev/null
+++ b/proposals/0000-trailing-block-expr.md
@@ -0,0 +1,152 @@
+# Trailing Block Expressions
+
+* Proposal: [HXP-NNNN](NNNN-filename.md)
+* Author: [Robert Borghese](https://github.com/RobertBorghese)
+
+## Introduction
+
+Allow block expressions to be appended to macro calls as final argument.
+```haxe
+macro function ifNotNull(expr: Expr, trail: TrailingExpr): Expr {
+ return macro {
+ final val = $expr;
+ if(val != null) {
+ $trail;
+ } else null;
+ }
+}
+
+// ---
+
+ifNotNull(generateData()) {
+ trace(val);
+}
+```
+
+## Motivation
+
+This feature has two driving motivations: clarify, optimize, and simplify a somewhat common pattern in Haxe metaprogramming, and open oppurtunities for better Haxe integrated DSLs.
+
+### Simplify
+
+There are currently two ways to replicate this functionality currently.
+
+First is by placing the block within the macro call: `myCall({ ... })`. This syntax is identical to a normal function with a block expression being used as a value, so it could be confusing. Not to mention it's uglier and clunkier to write. The proposed new syntax clarifies the expression is being processed through a macro, as it cannot be used elsewhere.
+
+The second method is with metadata: `@myMeta { ... }`. It looks nearly identical to the proposed syntax, but modifying the attached expression requires iterating through the entire project's AST to find the metadata using global `@:build`. Furthermore, metadata is untyped, so there is no namespace/import control, and no way to easily find the source reading the code. Meanwhile, a macro function is imported, typed, and can be sourced easily with IDE tools, so it is an objective improvement.
+
+### DSL
+
+In addition to the technical capabilities provided to macros, allowing the Haxe parser to accept trailing block expressions becomes a feature within itself. While not the conclusive answer to all DSL support, it provides a fast, easy, Haxe-ified, type-safe alternative.
+
+In fact, a decent number of newer languages' support for "DSL" seemingly boils down to allowing the parser to accept trailing blocks. See the "Macros to implement DSLs" section on this [official Nimlang blog](https://nim-lang.org/blog/2021/11/15/zen-of-nim.html), or check out this [Kotlin tutorial](https://kotlinlang.org/docs/type-safe-builders.html#scope-control-dslmarker) and [Kotlin article](https://medium.com/kotlin-and-kotlin-for-android/kotlin-dsl-coding-a-dsl-6-ee355be81106).
+
+```haxe
+// how an html builder in Haxe could look
+return html {
+ head {
+ title { "My Page"; }
+ }
+ body {
+ div {
+ p(style="mystyle") {
+ "Hello World";
+ }
+
+ button(onclick=Statics.whenButtonPress) {
+ "Click me";
+ }
+ }
+ }
+}
+```
+
+
+
+## Detailed design
+
+### Structure Changes
+
+A new `ExprDef` case should be added.
+```haxe
+ETrailingBlock(e: Expr, blockExpr: Expr)
+```
+
+And a new typedef named `TrailingExpr` should be added to `haxe.macro.Expr`. This is so a macro function can choose to accept a trailing block vs normal expression, while also ensuring `TrailingExpr`s are compatible with macro reification.
+```haxe
+typedef TrailingExpr = Expr;
+```
+
+
+
+### Typing Rules
+
+A trailing block is a metaprogramming feature. All instances of `ETrailingBlock` should be converted prior to the typing phase either using macro functions or `@:build` macros. If the typer encounters `ETrailingBlock`, an error is thrown (though, other alternatives to how to handle this are listed below).
+
+As a result, there is no `TypedExpr` equivalent for `ETrailingBlock`.
+
+
+
+### Declaration Rules
+
+For a macro function to accept a trailing expression, the last argument must be `TrailingExpr`.
+```haxe
+macro function useTrail(num: Int, e: Expr, trail: TrailingExpr); // valid
+macro function useTrail(trail: TrailingExpr, num: Int); // error: TrailingExpr must be last argument
+```
+
+`TrailingExpr` is incompatible with the new rest argument syntax (`...`). However, a macro function that uses both rest arguments and a trailing block expression can be created by making the second to last argument an `Array`, and the final argument `TrailingExpr`.
+```haxe
+macro function f(trail: TrailingExpr, ...e: Expr); // error: TrailingExpr must be last argument
+macro function f(trail: TrailingExpr, args: Array); // error: TrailingExpr must be last argument
+
+macro function f(e: Expr, args: Array); // macro function with Expr rest arguments
+macro function f(args: Array, e: Expr); // macro function that does NOT accept rest arguments
+macro function f(args: Array, trail: TrailingExpr); // macro function that acceps rest arguments AND trailing block
+```
+
+`TrailingExpr` is only allowed in macro functions.
+```haxe
+// error: haxe.macro.Expr.TrailingExpr only allowed in macro functions. Use haxe.macro.Expr instead.
+function f(trail: TrailingExpr);
+```
+
+
+
+### Syntax Rules
+
+A trailing block can be placed after any function call. Simply place a block expression directly after the closing parenthesis of the call.
+```haxe
+myCall(12, "string") {
+ // place block content here
+}
+```
+
+If the call expression does not have any arguments (besides the `TrailingExpr`), the parenthesis can be omitted.
+```haxe
+onlyOneArg {
+ // place block content here
+}
+```
+
+
+
+## Impact on existing code
+
+A new case is added to `haxe.macro.ExprDef`, so will break some macro code.
+
+The new syntax itself should not cause any issues with existing code, however.
+
+## Drawbacks
+
+Maybe certain syntax errors might not trigger the same? But the restrictive nature of the new syntax should ensure it's not an issue.
+
+## Alternatives
+
+See Motivation.
+
+## Unresolved questions
+
+Perhaps instead of using `TrailingExpr`, the trailing block can be used on any macro with an `Expr` as the final argument?
+
+Maybe the trailing expression can be used in normal functions, but when passing a function for the last argument similar to Kotlin?
From 38eb06610166996fcc461ec2aab73a023f2fe2be Mon Sep 17 00:00:00 2001
From: RoBBoR <28355157+RobertBorghese@users.noreply.github.com>
Date: Fri, 6 Jan 2023 09:11:57 -0500
Subject: [PATCH 2/2] Unresolved questions - `TrailingExpr` as abstract?
---
proposals/0000-trailing-block-expr.md | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/proposals/0000-trailing-block-expr.md b/proposals/0000-trailing-block-expr.md
index 1a85ca2..74001bd 100644
--- a/proposals/0000-trailing-block-expr.md
+++ b/proposals/0000-trailing-block-expr.md
@@ -147,6 +147,16 @@ See Motivation.
## Unresolved questions
-Perhaps instead of using `TrailingExpr`, the trailing block can be used on any macro with an `Expr` as the final argument?
+### Abstract vs Typedef
-Maybe the trailing expression can be used in normal functions, but when passing a function for the last argument similar to Kotlin?
+Instead of a typedef, `TrailingExpr` could be an abstract? That way some helpful functions can be provided:
+```haxe
+abstract TrailingExpr(Expr) to Expr {
+ // TrailingExpr is always an EBlock, so unwrap and return its contents
+ public function contents(): Array { ... }
+}
+```
+
+### Trailing Lambdas
+
+Perhaps trailing blocks should function as last argument lambdas for normal functions, similar to how Kotlin works?