Skip to content
Merged
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ All code examples and documentation MUST use gen2 syntax (add `options gen2` at
- **Tuple `=>` operator:** `a => b` creates a `tuple<auto;auto>` — useful in LINQ, table construction, and ad-hoc pairs
- **Bitfield variables** need explicit type for `.field` access and printing: `var f : MyBitfield`
- **Bitfield dot access:** read with `f.flag` (returns bool), write with `f.flag = true/false`
- **`typeinfo`** special syntax: `typeinfo enum_length(type<MyEnum>)`NOT `typeinfo(enum_length type<MyEnum>)`
- **`typeinfo`** gen2 syntax: trait name goes **outside** parentheses — `typeinfo trait_name(type<T>)`, NOT `typeinfo(trait_name type<T>)`. With subtrait: `typeinfo has_method<name>(type<T>)`. With two traits: `typeinfo trait<sub;extra>(type<T>)`
- **`static_if`:** `static_if (condition) { ... }` — parentheses required in gen2

### Important defaults
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ All code examples and documentation MUST use gen2 syntax (add `options gen2` at
- **Tuple `=>` operator:** `a => b` creates a `tuple<auto;auto>` — useful in LINQ, table construction, and ad-hoc pairs
- **Bitfield variables** need explicit type for `.field` access and printing: `var f : MyBitfield`
- **Bitfield dot access:** read with `f.flag` (returns bool), write with `f.flag = true/false`
- **`typeinfo`** special syntax: `typeinfo enum_length(type<MyEnum>)`NOT `typeinfo(enum_length type<MyEnum>)`
- **`typeinfo`** gen2 syntax: trait name goes **outside** parentheses — `typeinfo trait_name(type<T>)`, NOT `typeinfo(trait_name type<T>)`. With subtrait: `typeinfo has_method<name>(type<T>)`. With two traits: `typeinfo trait<sub;extra>(type<T>)`
- **`static_if`:** `static_if (condition) { ... }` — parentheses required in gen2

### Important defaults
Expand Down
2 changes: 1 addition & 1 deletion daslib/jobque_boost.das
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ class private ChannelAndStatusCapture : AstCaptureMacro {
)
return <- pCall
}
//! This macro implements capturing of the `jobque::Channel` and `jobque::JobStatus` types.
//! This macro implements capturing of the `jobque::Channel` and `jobque::JobStatus` types.
//! When captured reference counts are increased. When lambda is destroyed, reference counts are decreased.
def override captureExpression(prog : Program?; mod : Module?; expr : ExpressionPtr; typ : TypeDeclPtr) : ExpressionPtr {
//! Implementation details for the capture macro.
Expand Down
100 changes: 89 additions & 11 deletions doc/source/reference/language/macros.rst
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,31 @@ There is additionally the ``[enumeration_macro]`` annotation which accomplishes

``apply`` is invoked before the infer pass. It is the best time to modify the enumeration, generate some code, etc.

In gen2 syntax, register an enumeration macro and annotate enums with:

.. code-block:: das

[enumeration_macro(name="enum_total")]
class EnumTotalAnnotation : AstEnumerationAnnotation {
def override apply(var enu : EnumerationPtr; var group : ModuleGroup;
args : AnnotationArgumentList;
var errors : das_string) : bool {
// modify enu.list or generate code
return true
}
}

[enum_total]
enum Direction { North; South; East; West }

.. seealso::

Tutorial: :ref:`tutorial_macro_enumeration_macro` — step-by-step
enumeration macro examples (enum modification and code generation)

Standard library: ``daslib/enum_trait.das`` —
:ref:`enum_trait module reference <stdlib_enum_trait>`

---------------
AstVariantMacro
---------------
Expand Down Expand Up @@ -312,13 +337,15 @@ AstReaderMacro
``add_new_reader_macro`` adds a reader macro to a module.
There is additionally the ``[reader_macro]`` annotation, which essentially automates the same thing.

Reader macros accept characters, collect them if necessary, and return an ``ast::Expression``:
Reader macros accept characters, collect them if necessary, and produce output
via one of two patterns:

.. code-block:: das

class AstReaderMacro {
def abstract accept ( prog:ProgramPtr; mod:Module?; expr:ExprReader?; ch:int; info:LineInfo ) : bool
def abstract visit ( prog:ProgramPtr; mod:Module?; expr:smart_ptr<ExprReader> ) : ExpressionPtr
def abstract suffix ( prog:ProgramPtr; mod:Module?; expr:ExprReader?; info:LineInfo; var outLine:int&; var outFile:FileInfo?& ) : string
}

Reader macros are invoked via the ``% READER_MACRO_NAME ~ character_sequence`` syntax.
Expand Down Expand Up @@ -364,6 +391,20 @@ In ``visit``, the collected sequence is converted into a make array ``[ch1,ch2,.

More complex examples include the JsonReader macro in :ref:`daslib/json_boost <stdlib_json_boost>` or RegexReader in :ref:`daslib/regex_boost <stdlib_regex_boost>`.

``suffix`` is an alternative to ``visit`` — it is called immediately after ``accept`` during parsing,
before the AST is built. Instead of returning an AST node, it returns a **string** of daScript source
code that the parser re-parses. This is useful for generating top-level declarations (functions,
structs) from custom syntax. When used at module level the ``ExprReader`` node is discarded,
and the suffix text is the only output. ``SpoofInstanceReader`` in ``daslib/spoof.das`` is an example.

The ``outLine`` and ``outFile`` parameters allow remapping line information for error reporting in the
injected code.

.. seealso::

:ref:`Tutorial: Reader Macros <tutorial_macro_reader_macro>` — step-by-step example
of both visit and suffix patterns.

------------
AstCallMacro
------------
Expand Down Expand Up @@ -405,22 +446,32 @@ Note how the name is provided in the ``[call_macro]`` annotation.
AstPassMacro
------------

``AstPassMacro`` is one macro to rule them all. It gets entire module as an input,
and can be invoked at numerous passes:
``AstPassMacro`` is one macro to rule them all. It gets the entire program as
input and can be invoked at numerous passes:

.. code-block:: das

class AstPassMacro {
def abstract apply ( prog:ProgramPtr; mod:Module? ) : bool
def abstract apply(prog : ProgramPtr; mod : Module?) : bool
}

Five annotations control when a pass macro runs:

- ``[infer_macro]`` — after clean type inference. Returning ``true`` re-infers.
- ``[dirty_infer_macro]`` — during each dirty inference pass.
- ``[lint_macro]`` — after successful compilation (lint phase, read-only).
- ``[global_lint_macro]`` — same as ``[lint_macro]`` but for all modules.
- ``[optimization_macro]`` — during the optimisation loop.

``make_pass_macro`` registers a class as a pass macro.

``add_new_infer_macro`` adds a pass macro to the infer pass. The ``[infer]`` annotation accomplishes the same thing.
Typically, such macros create an ``AstVisitor`` which performs the necessary
transformations via ``visit(prog, adapter)``.

``add_new_dirty_infer_macro`` adds a pass macro to the ``dirty`` section of infer pass. The ``[dirty_infer]`` annotation accomplishes the same thing.
.. seealso::

Typically, such macros create an ``AstVisitor`` which performs the necessary transformations.
:ref:`tutorial_macro_pass_macro` — step-by-step tutorial with lint and
infer macro examples.

----------------
AstTypeInfoMacro
Expand All @@ -435,15 +486,27 @@ AstTypeInfoMacro
def abstract getAstType ( var lib:ModuleLibrary; expr:smart_ptr<ExprTypeInfo>; var errors:das_string ) : TypeDeclPtr
}

``add_new_typeinfo_macro`` adds a reader macro to a module.
``add_new_typeinfo_macro`` adds a typeinfo macro to a module.
There is additionally the ``[typeinfo_macro]`` annotation, which essentially automates the same thing.

``getAstChange`` returns a newly generated ast for the typeinfo expression.
The ``typeinfo`` expression uses gen2 syntax with the trait name **outside** the
parentheses::

typeinfo trait_name(type<T>) // basic
typeinfo trait_name<subtrait>(type<T>) // with subtrait
typeinfo trait_name<sub;extra>(type<T>) // with subtrait and extratrait

``getAstChange`` returns a newly generated AST node for the typeinfo expression.
Alternatively, it returns null if no changes are required, or if there is an error.
In case of error, the errors string must be filled.

``getAstType`` returns the type of the new typeinfo expression.

.. seealso::

Tutorial: :ref:`tutorial_macro_typeinfo_macro` — step-by-step guide with three
``getAstChange`` examples (struct description, enum names, method check).

---------------
AstForLoopMacro
---------------
Expand Down Expand Up @@ -472,14 +535,29 @@ AstCaptureMacro
class AstCaptureMacro {
def abstract captureExpression ( prog:Program?; mod:Module?; expr:ExpressionPtr; etype:TypeDeclPtr ) : ExpressionPtr
def abstract captureFunction ( prog:Program?; mod:Module?; var lcs:Structure?; var fun:FunctionPtr ) : void
def abstract releaseFunction ( prog:Program?; mod:Module?; var lcs:Structure?; var fun:FunctionPtr ) : void
}

``add_new_capture_macro`` adds a reader macro to a module.
There is additionally the ``[capture_macro]`` annotation, which essentially automates the same thing.

``captureExpression`` is called when an expression is captured. It returns a new expression, or null if no changes are required.
``captureExpression`` is called per captured variable when the lambda struct is being built.
It returns a replacement expression to wrap the capture, or null if no changes are required.

``captureFunction`` is called once after the lambda function is generated.
Use this to inspect captured fields (``lcs``) and append code to ``(fun.body as ExprBlock).finalList`` —
which runs **after each invocation** (per-call finally), not on destruction.

``releaseFunction`` is called once when the lambda **finalizer** is generated.
``fun`` is the finalizer function (not the lambda call function).
Code appended to ``(fun.body as ExprBlock).list`` runs on **destruction** —
after the user-written ``finally {}`` block but before the compiler-generated
field cleanup (``delete *__this``).

.. seealso::

``captureFunction`` is called when a function is captured. This is where custom finalization can be added to the ``final`` section of the function body.
:ref:`Tutorial: Capture Macros <tutorial_macro_capture_macro>` — step-by-step example
using all three hooks with an ``[audited]`` tag annotation.

----------------
AstCommentReader
Expand Down
7 changes: 6 additions & 1 deletion doc/source/reference/tutorials.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,9 @@ Run any tutorial from the project root::
tutorials/macros/06_structure_macro.rst
tutorials/macros/07_block_macro.rst
tutorials/macros/08_variant_macro.rst
tutorials/macros/09_for_loop_macro.rst
tutorials/macros/09_for_loop_macro.rst
tutorials/macros/10_capture_macro.rst
tutorials/macros/11_reader_macro.rst
tutorials/macros/12_typeinfo_macro.rst
tutorials/macros/13_enumeration_macro.rst
tutorials/macros/14_pass_macro.rst
38 changes: 38 additions & 0 deletions doc/source/reference/tutorials/14_lambdas.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,44 @@ A function can create and return a lambda that captures state::

Each call creates an independent counter.

Lambda lifecycle and finally blocks
=====================================

A lambda is a heap-allocated struct. The full lifecycle is:

1. **Capture** — outer variables are copied/moved/cloned into the struct
2. **Invoke** — the lambda body runs (may be called many times)
3. **Destroy** — when the lambda is deleted or garbage collected:
a) The ``finally{}`` block runs (user cleanup code)
b) Captured fields are finalized (compiler-generated ``delete``)
c) The struct memory is freed

Captured fields are automatically deleted on destruction unless:

- The field was captured **by reference** (not owned)
- The field was captured **by move/clone** with ``doNotDelete``
- The field type is POD (int, float — no cleanup needed)

Finally block
~~~~~~~~~~~~~

A lambda's ``finally{}`` block runs **once** when the lambda is
**destroyed** — not after each invocation. This is different from
a *block* ``finally{}``, which runs after every call::

var demo <- @() {
print("body\n")
} finally {
print("destroyed\n") // runs once, on deletion
}
demo() // prints "body"
demo() // prints "body"
unsafe { delete demo; } // prints "destroyed"

Use lambda ``finally{}`` for one-time destruction cleanup (releasing
resources, closing handles). For per-call cleanup, use scoped
variables or ``defer`` inside the lambda body.

Lambda vs block vs function pointer
=====================================

Expand Down
2 changes: 2 additions & 0 deletions doc/source/reference/tutorials/macros/09_for_loop_macro.rst
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ complex transformation but the same ``AstForLoopMacro`` pattern.

Previous tutorial: :ref:`tutorial_macro_variant_macro`

Next tutorial: :ref:`tutorial_macro_capture_macro`

Standard library: ``daslib/soa.das`` (``SoaForLoop`` macro)

Language reference: :ref:`Macros <macros>` — full macro system documentation
Loading