Work in Progress

You are viewing unfinished draft material right now.

Advanced Macros

This document builds on the introduction to stateful macros and introduces all remaining features. Covered topics include

Scoped States and Configuration Macros

When using Context.createState, you obtain getter and setter for state that is global with respect to the evaluation process. Oftentimes, you will want to scope state locally to be available only during evaluation of a certain macro. The Context.createScopedState function provides just that.

Whereas Context.createState returns only a getter and a setter, Context.createScopedState additionally returns a macro. Invoking that scoping macro initialises a new instance of the state, and the getter and setter are local to the invocations of the scoping macro. This even works when invoking the macro inside another invocation of itself — and the state initialisation function is called with the parent state as an argument in that case.

The following example demonstrates a <Quotes> macro that inserts quotation marks, and whose style of quotation marks changes based on an outer <QuoteStyle> macro. The <QuoteStyle> macro wraps its children in a state scoping macro and sets that state to a preferred style of quotation marks. The <Quotes> macro then simply uses the getter to select the right quotation style.

import { Children, Context, evaluate, Expression } from "macromania";
import { assertEquals } from "@std/assert";

type QuoteState = {opening: string, closing: string};

const [QuoteStateScope, getQuoteState, setQuoteState] = Context.createScopedState(() => ({opening: `"`, closing: `"`}));

function QuoteStyle({children, style}: {style: "plain" | "fancy", children?: Children}): Expression {
    return <QuoteStateScope>
        <effect fun={(ctx) => {
            if (style === "plain") {
                setQuoteState(ctx, {opening: `"`, closing: `"`});
            } else {
                setQuoteState(ctx, {opening: ``, closing: ``});
            }
            return <xs x={children}/> ;
        }}/>
    </QuoteStateScope>
}

function Quotes({children}: {children?: Children}): Expression {
    return <effect fun={(ctx) => {
        const quoteState = getQuoteState(ctx);
        return <>{quoteState.opening}<xs x={children}/>{quoteState.closing}</>
    }}/>
}

assertEquals(
  await evaluate(
    <>
      <QuoteStyle style="plain">
        <Quotes>hi</Quotes>
        <QuoteStyle style="fancy"><Quotes>salutations</Quotes></QuoteStyle>
        <Quotes>hi</Quotes>
      </QuoteStyle>
      {/* uses the initial state outside any scope macro */}
      <Quotes>hi</Quotes>
    </>
  ),
  `"hi"“salutations”"hi""hi"`,
);

Analogous to Context.createAsyncState, there is also the Context.createAsyncScopedState function for creating scoped states with async initilisation functions and getters.

TODO config macros

Work in progress (notes below).