@nml-lang/compiler-ts 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/lexer.d.ts +30 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +132 -0
- package/dist/lexer.js.map +1 -0
- package/dist/parser.d.ts +55 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +915 -0
- package/dist/parser.js.map +1 -0
- package/dist/renderer.d.ts +19 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +186 -0
- package/dist/renderer.js.map +1 -0
- package/package.json +43 -0
- package/src/index.ts +116 -0
- package/src/lexer.ts +186 -0
- package/src/parser.ts +1108 -0
- package/src/renderer.ts +236 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nml/compiler-ts — public API
|
|
3
|
+
*
|
|
4
|
+
* Exports the CompilerAdapter interface so consumers (CLI, Vite plugin,
|
|
5
|
+
* Cloudflare Worker, MCP server) always program against the interface,
|
|
6
|
+
* never a concrete class. A future @nml/compiler-wasm package will export
|
|
7
|
+
* a WasmCompiler satisfying the same interface.
|
|
8
|
+
*/
|
|
9
|
+
import { buildAst, renderVariables, NMLParserError, findComponentRootNode, parseLineRaw, isTruthy, postProcessConditionalsPass } from "./parser.js";
|
|
10
|
+
import { generateHtml, type RenderOptions } from "./renderer.js";
|
|
11
|
+
import type { ASTNode, ComponentMap, GlobalStyles } from "./parser.js";
|
|
12
|
+
export interface CompilerAdapter {
|
|
13
|
+
/**
|
|
14
|
+
* Compile a raw NML string to an HTML string.
|
|
15
|
+
* Returns a Promise — async to support @include directives that read
|
|
16
|
+
* files from the filesystem, Cloudflare R2, D1, or in-memory stores.
|
|
17
|
+
*
|
|
18
|
+
* @param input - Raw NML source
|
|
19
|
+
* @param context - Optional template variable context
|
|
20
|
+
* @param options - Optional components, global styles, readFile, basePath
|
|
21
|
+
*/
|
|
22
|
+
render(input: string, context?: Record<string, unknown>, options?: CompileOptions): Promise<string>;
|
|
23
|
+
}
|
|
24
|
+
export interface CompileOptions {
|
|
25
|
+
components?: ComponentMap;
|
|
26
|
+
globalStyles?: GlobalStyles;
|
|
27
|
+
/** Async function to read included files. Required when NML uses @include. */
|
|
28
|
+
readFile?: (path: string) => Promise<string>;
|
|
29
|
+
/** Absolute path of the NML file being rendered — resolves relative @include paths. */
|
|
30
|
+
basePath?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare const nmlCompiler: CompilerAdapter;
|
|
33
|
+
export { buildAst, generateHtml, renderVariables, NMLParserError, findComponentRootNode, parseLineRaw, isTruthy, postProcessConditionalsPass };
|
|
34
|
+
export type { RenderOptions };
|
|
35
|
+
export type { ASTNode, ComponentMap, GlobalStyles };
|
|
36
|
+
export { NMLLexerError } from "./lexer.js";
|
|
37
|
+
export type { SourceLocation, Token, TokenKind } from "./lexer.js";
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,qBAAqB,EAAE,YAAY,EAAE,QAAQ,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAC;AACpJ,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMvE,MAAM,WAAW,eAAe;IAC9B;;;;;;;;OAQG;IACH,MAAM,CACJ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,CAAC,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAgED,eAAO,MAAM,WAAW,EAAE,eAAkC,CAAC;AAM7D,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,qBAAqB,EAAE,YAAY,EAAE,QAAQ,EAAE,2BAA2B,EAAE,CAAC;AAC/I,YAAY,EAAE,aAAa,EAAE,CAAC;AAC9B,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nml/compiler-ts — public API
|
|
3
|
+
*
|
|
4
|
+
* Exports the CompilerAdapter interface so consumers (CLI, Vite plugin,
|
|
5
|
+
* Cloudflare Worker, MCP server) always program against the interface,
|
|
6
|
+
* never a concrete class. A future @nml/compiler-wasm package will export
|
|
7
|
+
* a WasmCompiler satisfying the same interface.
|
|
8
|
+
*/
|
|
9
|
+
import { buildAst, renderVariables, NMLParserError, findComponentRootNode, parseLineRaw, isTruthy, postProcessConditionalsPass } from "./parser.js";
|
|
10
|
+
import { generateHtml } from "./renderer.js";
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Default TypeScript engine
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
class TSCompiler {
|
|
15
|
+
async render(input, context = {}, options = {}) {
|
|
16
|
+
const components = options.components ?? {};
|
|
17
|
+
const globalStyles = options.globalStyles ?? {};
|
|
18
|
+
const renderOpts = {
|
|
19
|
+
readFile: options.readFile,
|
|
20
|
+
basePath: options.basePath,
|
|
21
|
+
};
|
|
22
|
+
const ast = buildAst(input, { components, globalStyles });
|
|
23
|
+
let html = await generateHtml(ast, 0, context, renderOpts);
|
|
24
|
+
// Inject scoped styles if any are present
|
|
25
|
+
if (Object.keys(globalStyles).length > 0) {
|
|
26
|
+
const usedScopeIds = collectScopeIds(ast);
|
|
27
|
+
const usedStyles = [];
|
|
28
|
+
for (const sid of usedScopeIds) {
|
|
29
|
+
if (sid in globalStyles) {
|
|
30
|
+
usedStyles.push(globalStyles[sid]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (usedStyles.length > 0) {
|
|
34
|
+
const styleTag = `<style data-nml-scoped-styles>\n${usedStyles.join("\n")}\n</style>`;
|
|
35
|
+
if (html.includes("</head>")) {
|
|
36
|
+
html = html.replace("</head>", `${styleTag}\n</head>`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
html = styleTag + "\n" + html;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return html;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function collectScopeIds(nodes) {
|
|
47
|
+
const ids = new Set();
|
|
48
|
+
function walk(items) {
|
|
49
|
+
for (const n of items) {
|
|
50
|
+
for (const k of Object.keys(n.attributes)) {
|
|
51
|
+
if (k.startsWith("nml-c-"))
|
|
52
|
+
ids.add(k);
|
|
53
|
+
}
|
|
54
|
+
if (n.children.length > 0)
|
|
55
|
+
walk(n.children);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
walk(nodes);
|
|
59
|
+
return ids;
|
|
60
|
+
}
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Singleton default export — V8-optimised TS engine
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
export const nmlCompiler = new TSCompiler();
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Re-exports for advanced consumers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
export { buildAst, generateHtml, renderVariables, NMLParserError, findComponentRootNode, parseLineRaw, isTruthy, postProcessConditionalsPass };
|
|
69
|
+
export { NMLLexerError } from "./lexer.js";
|
|
70
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/lexer.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NML Lexer
|
|
3
|
+
* Tokenizes raw NML source text into a stream of typed tokens,
|
|
4
|
+
* each carrying precise source location (line, column) for error
|
|
5
|
+
* reporting and Vite overlay / MCP diagnostics.
|
|
6
|
+
*/
|
|
7
|
+
export interface SourceLocation {
|
|
8
|
+
line: number;
|
|
9
|
+
column: number;
|
|
10
|
+
}
|
|
11
|
+
export type TokenKind = "INDENT" | "ELEMENT" | "ATTR_CHAIN" | "MULTILINE_START" | "MULTILINE_LINE" | "COMMENT" | "BLANK" | "EOF";
|
|
12
|
+
export interface Token {
|
|
13
|
+
kind: TokenKind;
|
|
14
|
+
value: string;
|
|
15
|
+
loc: SourceLocation;
|
|
16
|
+
/** For INDENT tokens: indentation level (0-based, each level = 4 spaces) */
|
|
17
|
+
level?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Tokenize NML source into a flat array of line-level tokens.
|
|
21
|
+
* Each "line" of NML produces at most one primary token; multiline
|
|
22
|
+
* blocks consume multiple raw lines and emit MULTILINE_LINE tokens
|
|
23
|
+
* for each.
|
|
24
|
+
*/
|
|
25
|
+
export declare function tokenize(source: string): Token[];
|
|
26
|
+
export declare class NMLLexerError extends Error {
|
|
27
|
+
loc: SourceLocation;
|
|
28
|
+
constructor(message: string, loc: SourceLocation);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=lexer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../src/lexer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,SAAS,GACjB,QAAQ,GACR,SAAS,GACT,YAAY,GACZ,iBAAiB,GACjB,gBAAgB,GAChB,SAAS,GACT,OAAO,GACP,KAAK,CAAC;AAEV,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,cAAc,CAAC;IACpB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,CA8GhD;AA8BD,qBAAa,aAAc,SAAQ,KAAK;IACtC,GAAG,EAAE,cAAc,CAAC;gBACR,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc;CAKjD"}
|
package/dist/lexer.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NML Lexer
|
|
3
|
+
* Tokenizes raw NML source text into a stream of typed tokens,
|
|
4
|
+
* each carrying precise source location (line, column) for error
|
|
5
|
+
* reporting and Vite overlay / MCP diagnostics.
|
|
6
|
+
*/
|
|
7
|
+
const INDENT_WIDTH = 4;
|
|
8
|
+
/**
|
|
9
|
+
* Tokenize NML source into a flat array of line-level tokens.
|
|
10
|
+
* Each "line" of NML produces at most one primary token; multiline
|
|
11
|
+
* blocks consume multiple raw lines and emit MULTILINE_LINE tokens
|
|
12
|
+
* for each.
|
|
13
|
+
*/
|
|
14
|
+
export function tokenize(source) {
|
|
15
|
+
const tokens = [];
|
|
16
|
+
const rawLines = source.split("\n");
|
|
17
|
+
let lineIdx = 0; // 0-based index into rawLines
|
|
18
|
+
while (lineIdx < rawLines.length) {
|
|
19
|
+
const rawLine = rawLines[lineIdx];
|
|
20
|
+
const currentLine = lineIdx + 1; // 1-based
|
|
21
|
+
const stripped = rawLine.trimEnd();
|
|
22
|
+
// Blank line
|
|
23
|
+
if (stripped.trim() === "") {
|
|
24
|
+
tokens.push({ kind: "BLANK", value: "", loc: { line: currentLine, column: 0 } });
|
|
25
|
+
lineIdx++;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
// Comment
|
|
29
|
+
if (stripped.trimStart().startsWith("//")) {
|
|
30
|
+
const col = stripped.length - stripped.trimStart().length;
|
|
31
|
+
tokens.push({ kind: "COMMENT", value: stripped.trimStart(), loc: { line: currentLine, column: col } });
|
|
32
|
+
lineIdx++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
// Validate and measure indentation
|
|
36
|
+
const leadingSpaces = stripped.length - stripped.trimStart().length;
|
|
37
|
+
if (stripped.startsWith("\t")) {
|
|
38
|
+
throw new NMLLexerError(`Indentation error on line ${currentLine}: Please use 4 spaces for indentation, not tabs.`, { line: currentLine, column: 0 });
|
|
39
|
+
}
|
|
40
|
+
if (leadingSpaces % INDENT_WIDTH !== 0) {
|
|
41
|
+
throw new NMLLexerError(`Indentation error on line ${currentLine}: Non-standard indentation. Use 4 spaces per level.`, { line: currentLine, column: 0 });
|
|
42
|
+
}
|
|
43
|
+
const level = leadingSpaces / INDENT_WIDTH;
|
|
44
|
+
const content = stripped.trimStart();
|
|
45
|
+
// Check if this line is a multiline trigger (ends with ':' outside quotes)
|
|
46
|
+
const isMultilineTrigger = isLineMultilineTrigger(content);
|
|
47
|
+
// Emit INDENT + ELEMENT token for this line
|
|
48
|
+
tokens.push({
|
|
49
|
+
kind: "INDENT",
|
|
50
|
+
value: "",
|
|
51
|
+
level,
|
|
52
|
+
loc: { line: currentLine, column: 0 },
|
|
53
|
+
});
|
|
54
|
+
tokens.push({
|
|
55
|
+
kind: isMultilineTrigger ? "MULTILINE_START" : "ELEMENT",
|
|
56
|
+
value: content,
|
|
57
|
+
loc: { line: currentLine, column: leadingSpaces },
|
|
58
|
+
});
|
|
59
|
+
lineIdx++;
|
|
60
|
+
// If multiline, consume subsequent indented lines
|
|
61
|
+
if (isMultilineTrigger) {
|
|
62
|
+
const blockStartLevel = level + 1;
|
|
63
|
+
const blockStartCol = blockStartLevel * INDENT_WIDTH;
|
|
64
|
+
while (lineIdx < rawLines.length) {
|
|
65
|
+
const mlRaw = rawLines[lineIdx];
|
|
66
|
+
const mlLine = lineIdx + 1;
|
|
67
|
+
const mlStripped = mlRaw.trimEnd();
|
|
68
|
+
// Blank lines are part of the block
|
|
69
|
+
if (mlStripped.trim() === "") {
|
|
70
|
+
tokens.push({ kind: "MULTILINE_LINE", value: "", loc: { line: mlLine, column: 0 } });
|
|
71
|
+
lineIdx++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const mlSpaces = mlStripped.length - mlStripped.trimStart().length;
|
|
75
|
+
// Block ends when indentation returns to block level or less
|
|
76
|
+
if (mlSpaces < blockStartCol) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
// Validate indentation within block
|
|
80
|
+
if (mlSpaces % INDENT_WIDTH !== 0) {
|
|
81
|
+
throw new NMLLexerError(`Indentation error on line ${mlLine}: Non-standard indentation. Use 4 spaces per level.`, { line: mlLine, column: 0 });
|
|
82
|
+
}
|
|
83
|
+
// Relative indent (strip the block-start indentation)
|
|
84
|
+
const relativeIndent = " ".repeat(mlSpaces - blockStartCol);
|
|
85
|
+
const mlContent = relativeIndent + mlStripped.trimStart();
|
|
86
|
+
tokens.push({
|
|
87
|
+
kind: "MULTILINE_LINE",
|
|
88
|
+
value: mlContent,
|
|
89
|
+
loc: { line: mlLine, column: mlSpaces },
|
|
90
|
+
});
|
|
91
|
+
lineIdx++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
tokens.push({ kind: "EOF", value: "", loc: { line: rawLines.length + 1, column: 0 } });
|
|
96
|
+
return tokens;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Returns true if the NML line (already stripped of indent) ends with ':'
|
|
100
|
+
* outside of any quoted string — indicating a multiline content block.
|
|
101
|
+
*/
|
|
102
|
+
function isLineMultilineTrigger(content) {
|
|
103
|
+
let inQuote = false;
|
|
104
|
+
let quoteChar = "";
|
|
105
|
+
let i = 0;
|
|
106
|
+
// Strip trailing whitespace before checking
|
|
107
|
+
const trimmed = content.trimEnd();
|
|
108
|
+
while (i < trimmed.length) {
|
|
109
|
+
const ch = trimmed[i];
|
|
110
|
+
if (inQuote) {
|
|
111
|
+
if (ch === quoteChar)
|
|
112
|
+
inQuote = false;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
if (ch === '"' || ch === "'") {
|
|
116
|
+
inQuote = true;
|
|
117
|
+
quoteChar = ch;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
i++;
|
|
121
|
+
}
|
|
122
|
+
return !inQuote && trimmed.endsWith(":");
|
|
123
|
+
}
|
|
124
|
+
export class NMLLexerError extends Error {
|
|
125
|
+
loc;
|
|
126
|
+
constructor(message, loc) {
|
|
127
|
+
super(message);
|
|
128
|
+
this.name = "NMLLexerError";
|
|
129
|
+
this.loc = loc;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=lexer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lexer.js","sourceRoot":"","sources":["../src/lexer.ts"],"names":[],"mappings":"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"}
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NML Parser
|
|
3
|
+
* Ports nml_parse.py's build_ast, _expand_components_pass,
|
|
4
|
+
* _inject_slot, and related helpers to TypeScript.
|
|
5
|
+
*
|
|
6
|
+
* Every ASTNode carries loc: { line, column } sourced from the lexer.
|
|
7
|
+
*/
|
|
8
|
+
import { type SourceLocation } from "./lexer.js";
|
|
9
|
+
export interface SourceLocation2 extends SourceLocation {
|
|
10
|
+
}
|
|
11
|
+
export interface ASTNode {
|
|
12
|
+
element: string;
|
|
13
|
+
attributes: Record<string, string | boolean | string[]>;
|
|
14
|
+
content: string;
|
|
15
|
+
children: ASTNode[];
|
|
16
|
+
multiline_trigger: boolean;
|
|
17
|
+
multiline_content: string[];
|
|
18
|
+
loc: SourceLocation;
|
|
19
|
+
/** Internal: node-level context override (props) */
|
|
20
|
+
__context__?: Record<string, unknown>;
|
|
21
|
+
/** Set by postProcessConditionalsPass on @if nodes: the else-branch children */
|
|
22
|
+
elseBranch?: ASTNode[];
|
|
23
|
+
}
|
|
24
|
+
export type ComponentMap = Record<string, ASTNode[]>;
|
|
25
|
+
export type GlobalStyles = Record<string, string>;
|
|
26
|
+
export declare class NMLParserError extends Error {
|
|
27
|
+
loc: SourceLocation;
|
|
28
|
+
constructor(message: string, loc?: SourceLocation);
|
|
29
|
+
}
|
|
30
|
+
export declare function parseLine(rawContent: string, loc: SourceLocation): ASTNode;
|
|
31
|
+
/**
|
|
32
|
+
* The Python parser supports `h1("Hello")` as shorthand for content.
|
|
33
|
+
* In NML the element line is like `h1.class("blue", "Hello")` or `h1("Hello")`.
|
|
34
|
+
* We need to detect when the "attribute name" equals the element name
|
|
35
|
+
* and treat the value as content instead.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseLineRaw(rawLine: string, loc: SourceLocation): ASTNode;
|
|
38
|
+
export declare function buildAst(source: string, options?: {
|
|
39
|
+
components?: ComponentMap;
|
|
40
|
+
globalStyles?: GlobalStyles;
|
|
41
|
+
}): ASTNode[];
|
|
42
|
+
export declare function findComponentRootNode(ast: ASTNode[]): ASTNode | null;
|
|
43
|
+
export declare function isTruthy(val: unknown): boolean;
|
|
44
|
+
export declare function renderVariables(template: string, context: Record<string, unknown>): string;
|
|
45
|
+
/**
|
|
46
|
+
* Because NML is indentation-based, @else / @endif appear as SIBLINGS of @if
|
|
47
|
+
* in the parent array (not as children). This pass:
|
|
48
|
+
* - Finds @if nodes at index i
|
|
49
|
+
* - Looks ahead for @else at i+1 → moves its children into node.elseBranch
|
|
50
|
+
* - Removes the @else and @endif siblings
|
|
51
|
+
* - Removes @endeach siblings (children already captured by indentation)
|
|
52
|
+
* - Recurses into every node's children array
|
|
53
|
+
*/
|
|
54
|
+
export declare function postProcessConditionalsPass(nodes: ASTNode[]): ASTNode[];
|
|
55
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAA2B,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAO1E,MAAM,WAAW,eAAgB,SAAQ,cAAc;CAAG;AAE1D,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC,CAAC;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,GAAG,EAAE,cAAc,CAAC;IACpB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,gFAAgF;IAChF,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC;CACxB;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;AACrD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAElD,qBAAa,cAAe,SAAQ,KAAK;IACvC,GAAG,EAAE,cAAc,CAAC;gBACR,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,cAAuC;CAK1E;AAqCD,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAoH1E;AAoGD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAsE1E;AAMD,wBAAgB,QAAQ,CACtB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IACP,UAAU,CAAC,EAAE,YAAY,CAAC;IAC1B,YAAY,CAAC,EAAE,YAAY,CAAC;CACxB,GACL,OAAO,EAAE,CA2HX;AAwMD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,OAAO,GAAG,IAAI,CAOpE;AAiDD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAM9C;AA+BD,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAmCR;AAuGD;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAmEvE"}
|