Tagscript

Parsers

Every tag the interpreter understands comes from a parser you registered.

A parser implements one tag, or a family of related tags. The interpreter has none of its own, so a fresh new Interpreter() renders plain text and leaves every {tag} exactly as written. What a template can do is precisely the list of parsers you pass in.

import { Interpreter, RandomParser } from 'tagscript';

const ts = new Interpreter(new RandomParser());

(await ts.run('{random:heads,tails}')).body; // 'tails'
(await ts.run('{if(1==1):yes|no}')).body; // '{if(1==1):yes|no}'

Add or replace them later with ts.addParsers(...) and ts.setParsers(...).

Built-in parsers

Logic and control flow

PageTagsWhat it does
If statementifPick one of two messages from a comparison.
Union statementany, or, unionTrue when any expression holds.
Intersection statementall, and, intersectionTrue when every expression holds.
Stopstop, halt, errorEnd the render and return a message.
BreakbreakReplace the output but keep parsing.

Variables

PageTagsWhat it does
Variablesany seeded nameRead values your app supplied.
Define=, assign, let, varName a value and reuse it later.
JSONjsonTurn a JSON payload into a named variable.

Text

PageTagsWhat it does
Formattinglower, upper, capitalize, escape, ordChange case, escape syntax, ordinals.
Includesin, contain, index, lindexSearch text and report position.
ReplacereplaceSwap one string for another.
Sliceslice, substr, substringCut out a substring.
URL encodingurlencode, encodeuri, urldecodeEncode text for a URL.

Randomness

PageTagsWhat it does
Randomrandom, randPick one item from a list.
Rangerange, rangefPick a number between two bounds.
Fifty fifty5050, 50, ?Render the payload on a coin flip.

For Discord specific tags such as embed, cooldown and require, see the Discord plugin.

Writing your own

Implement IParser, or extend BaseParser to get tag name matching and the parameter and payload checks for free.

import { BaseParser, type Context, type IParser } from 'tagscript';

export class FetchParser extends BaseParser implements IParser {
	public constructor() {
		// accepted names, requires a parameter, requires a payload
		super(['fetch'], false, true);
	}

	public async parse(ctx: Context) {
		const response = await fetch(ctx.tag.payload!.trim());
		return response.text();
	}
}

BaseParser takes three constructor arguments: the tag names it answers to, whether a parameter is required, and whether a payload is required. When a requirement is not met the parser declines the tag, and the interpreter leaves it in the output as written.

Both parse and willAccept may return a promise. parse returns the string that replaces the tag, or null to decline it after the fact, in which case the next parser that accepted the tag gets a turn.

A parser runs on text an untrusted author wrote. The FetchParser above will happily request any URL the template names, including one on your internal network. Validate the payload before acting on it.

Recording an action instead of text

To ask the host app to do something, write to ctx.response.actions and return an empty string. The interpreter never acts on it, so your code stays in control of what actually happens.

import { BaseParser, type Context, type IParser } from 'tagscript';

declare module 'tagscript' {
	interface IActions {
		notify?: { channel: string };
	}
}

export class NotifyParser extends BaseParser implements IParser {
	public constructor() {
		super(['notify'], true);
	}

	public parse(ctx: Context) {
		ctx.response.actions.notify = { channel: ctx.tag.parameter! };
		return '';
	}
}

Read response.actions.notify after the render and decide whether to honour it.

API reference

IParser, BaseParser, Context

Last updated on

On this page

Edit on Github