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
- locally scoped, nestable state,
- logging and source code positions, and
- tracking where in the input an expression came from.
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 macrosWork in progress (notes below).
- scoped states, config, async variants
- logging, currentDebug and friends, debug and friends, log, logEmptyLine, loggingGroup, printStack, fmtCode and friends (and context.fmt)
- debug information (getCurrentDebuggingInformation and getDebuggingStack)
- input tree positions (getEvaluationTreePosition, and the EvaluationTreePosition class)
- misc (getRound, halt, mustMakeProgress)