Blame Raw
· · · 228 lines (7.2 KB)
0 contributors
1/**
2 * Macro Execution Flags - modifiers that change how macros are resolved at runtime.
3 *
4 * Flags are special symbols placed between the opening braces `{{` and the macro identifier.
5 * Example: `{{!user}}` - the `!` is an "immediate resolve" flag.
6 *
7 * Multiple flags can be combined: `{{!?myMacro}}` or `{{ ! ? myMacro }}`
8 */
9
10/**
11 * @typedef {Object} MacroFlags
12 * @property {boolean} immediate - Whether the immediate (`!`) flag is set.
13 * @property {boolean} delayed - Whether the delayed (`?`) flag is set.
14 * @property {boolean} reevaluate - Whether the re-evaluate (`~`) flag is set.
15 * @property {boolean} filter - Whether the filter (`>`) flag is set.
16 * @property {boolean} closingBlock - Whether the closing block (`/`) flag is set.
17 * @property {boolean} preserveWhitespace - Whether the preserve whitespace (`#`) flag is set.
18 * @property {string[]} raw - The raw flag symbols in order of appearance.
19 */
20
21/**
22 * Enum of all recognized macro execution flags.
23 *
24 * @readonly
25 * @enum {string}
26 */
27export const MacroFlagType = Object.freeze({
28 /**
29 * Immediate resolve flag (`!`).
30 * This macro will be resolved first (in order of appearance) before "normal" macros.
31 * @status TBD - Not implemented in v1
32 */
33 IMMEDIATE: '!',
34
35 /**
36 * Delayed resolve flag (`?`).
37 * This macro will be resolved last (in order of appearance) after "normal" macros.
38 * @status TBD - Not implemented in v1
39 */
40 DELAYED: '?',
41
42 /**
43 * Re-evaluate flag (`~`).
44 * Marks a macro for potential re-evaluation.
45 * @status TBD - Not implemented in v1
46 */
47 REEVALUATE: '~',
48
49 /**
50 * Filter/pipe flag (`>`).
51 * Indicates that this macro should resolve `|` characters as output filters.
52 * @status Parsed - Filter feature not yet implemented
53 */
54 FILTER: '>',
55
56 /**
57 * Closing block flag (`/`).
58 * Marks this macro as the closing block of a scoped macro with the same identifier.
59 * A closing block macro does not support arguments itself.
60 * Example: `{{setvar::myvar}}long text{{/setvar}}`
61 * @status Implemented - Content between opening and closing tags becomes the last unnamed argument
62 */
63 CLOSING_BLOCK: '/',
64
65 /**
66 * Preserve whitespace flag (`#`).
67 * Prevents automatic trimming of scoped content.
68 * By default, scoped macro content is trimmed. Use this flag to preserve leading/trailing whitespace.
69 * Also provides backwards compatibility with legacy handlebars-style syntax like `{{#if ...}}`.
70 * Example: `{{#setvar::myvar}} content with spaces {{/setvar}}`
71 * @status Implemented - Prevents auto-trim on scoped content
72 */
73 PRESERVE_WHITESPACE: '#',
74
75 // Note: Variable shorthand (. and $) are NOT flags - they are special prefixes
76 // that trigger the variable expression parsing branch. See MacroLexer.js Var tokens.
77});
78
79/**
80 * @typedef {Object} MacroFlagDefinition
81 * @property {MacroFlagType} type - The flag type enum value (also the symbol).
82 * @property {string} name - Human-readable name for the flag.
83 * @property {string} description - Description of what the flag does.
84 * @property {boolean} implemented - Whether this flag's behavior is implemented.
85 * @property {boolean} affectsParser - Whether this flag changes parsing behavior (e.g., filter flag).
86 */
87
88/**
89 * Definitions for all macro flags with metadata.
90 *
91 * @type {Map<string, MacroFlagDefinition>}
92 */
93export const MacroFlagDefinitions = new Map([
94 [MacroFlagType.IMMEDIATE, {
95 type: MacroFlagType.IMMEDIATE,
96 name: 'Immediate',
97 description: 'Resolve this macro before other macros in the same text.',
98 implemented: false,
99 affectsParser: false,
100 }],
101 [MacroFlagType.DELAYED, {
102 type: MacroFlagType.DELAYED,
103 name: 'Delayed',
104 description: 'Resolve this macro after other macros in the same text.',
105 implemented: false,
106 affectsParser: false,
107 }],
108 [MacroFlagType.REEVALUATE, {
109 type: MacroFlagType.REEVALUATE,
110 name: 'Re-evaluate',
111 description: 'Mark this macro for re-evaluation.',
112 implemented: false,
113 affectsParser: false,
114 }],
115 [MacroFlagType.FILTER, {
116 type: MacroFlagType.FILTER,
117 name: 'Filter',
118 description: 'Enable pipe-based output filters for this macro.',
119 implemented: false,
120 affectsParser: true, // Changes how `|` is parsed
121 }],
122 [MacroFlagType.CLOSING_BLOCK, {
123 type: MacroFlagType.CLOSING_BLOCK,
124 name: 'Closing Block',
125 description: 'Marks this as a closing block for a scoped macro.',
126 implemented: true,
127 affectsParser: false,
128 }],
129 [MacroFlagType.PRESERVE_WHITESPACE, {
130 type: MacroFlagType.PRESERVE_WHITESPACE,
131 name: 'Preserve Whitespace',
132 description: 'Prevent automatic trimming of scoped content (legacy # syntax).',
133 implemented: true,
134 affectsParser: false,
135 }],
136]);
137
138/**
139 * Set of all valid flag symbols for quick lookup.
140 *
141 * @type {Set<string>}
142 */
143export const ValidFlagSymbols = new Set(Object.values(MacroFlagType));
144
145/**
146 * Creates a default MacroFlags object with all flags set to false.
147 *
148 * @returns {MacroFlags}
149 */
150export function createEmptyFlags() {
151 return {
152 immediate: false,
153 delayed: false,
154 reevaluate: false,
155 filter: false,
156 closingBlock: false,
157 preserveWhitespace: false,
158 raw: [],
159 };
160}
161
162/**
163 * Parses an array of flag symbols into a MacroFlags object.
164 *
165 * @param {string[]} flagSymbols - Array of flag symbol strings (e.g., ['!', '?']).
166 * @returns {MacroFlags}
167 */
168export function parseFlags(flagSymbols) {
169 const flags = createEmptyFlags();
170
171 for (const symbol of flagSymbols) {
172 switch (symbol) {
173 case MacroFlagType.IMMEDIATE:
174 flags.immediate = true;
175 break;
176 case MacroFlagType.DELAYED:
177 flags.delayed = true;
178 break;
179 case MacroFlagType.REEVALUATE:
180 flags.reevaluate = true;
181 break;
182 case MacroFlagType.FILTER:
183 flags.filter = true;
184 break;
185 case MacroFlagType.CLOSING_BLOCK:
186 flags.closingBlock = true;
187 break;
188 case MacroFlagType.PRESERVE_WHITESPACE:
189 flags.preserveWhitespace = true;
190 break;
191 default:
192 console.warn(`Can't parse unknown macro flag: ${symbol}`);
193 }
194 flags.raw.push(symbol);
195 }
196
197 return flags;
198}
199
200/**
201 * Checks if a MacroFlags object has any flags set.
202 *
203 * @param {MacroFlags} flags - The flags object to check.
204 * @returns {boolean} True if at least one flag is set.
205 */
206export function hasAnyFlag(flags) {
207 return flags.raw.length > 0;
208}
209
210/**
211 * Gets the flag definition for a given symbol.
212 *
213 * @param {string} symbol - The flag symbol (e.g., '!').
214 * @returns {MacroFlagDefinition|undefined}
215 */
216export function getFlagDefinition(symbol) {
217 return MacroFlagDefinitions.get(symbol);
218}
219
220/**
221 * Checks if a given symbol is a valid macro flag.
222 *
223 * @param {string} symbol - The symbol to check.
224 * @returns {boolean}
225 */
226export function isValidFlag(symbol) {
227 return ValidFlagSymbols.has(symbol);
228}