npm / flash-parser v0.0.1

flash-parser

A lossless CST + typed AST parser for the flash-theater DSL (.thr/.flsh) — the counterpart to kopytko-brightscript-parser for BrightScript. Used by flash-theater-compiler as its only parsing layer.

Install

terminal
npm install flash-parser

Requires Node.js ≥ 24.

Quick start

Parse a .thr file into a lossless CST + typed AST.

example.ts
import { parseThr, findAll, SyntaxKind, FieldDeclaration } from 'flash-parser';

const source = `
<script>
field count: integer = 0
derived doubled: integer = count * 2
</script>
<component>
  <Label id="label" text="{doubled}" />
</component>
`;

const { root, diagnostics } = parseThr(source);

diagnostics; // [] — no parse errors

// Lossless CST: printing every token's full text (with trivia) reproduces
// the exact original source, byte for byte.
root.getText() === source; // true

// findAll + one of the typed AST classes from 'ast.js' — walk() itself
// is a plain (node: SyntaxNode) => void callback over every CST node,
// not a per-kind visitor object; findAll is how you collect just one kind.
const fields = findAll(root, SyntaxKind.FieldDeclaration, (n) => new FieldDeclaration(n));
for (const f of fields) console.log(f.name, f.type, f.defaultLiteral);
// "count integer 0"

Diagnostics

parseThr/parseFlsh never throw on malformed source — a syntax problem always comes back as an entry in diagnostics instead (this is also what flash-theater-compiler's own CompileError wraps diagnostics[0] into, on top of this package).

example.ts
import { parseThr } from 'flash-parser';

// parseThr/parseFlsh never throw on a malformed file — check the
// diagnostics array yourself (this is what the compiler's own
// CompileError wraps diagnostics[0] into, on top of this package).
const { diagnostics } = parseThr(`
<script>
field count: integer = 0
<component></component>
`); // missing </script>

for (const d of diagnostics) {
  console.log(d.code, '—', d.message, `(line ${d.line})`);
}
// "thr/unterminated-script — No closing </script> found. (line 1)"

// ParseDiagnostic shape:
// { code: string, message: string, pos: number, end: number, line: number }
// pos/end are byte offsets into the source; line is 0-based.

What this package owns

DSL grammar

The .thr <script>/template split, field/derived/state/read/watch, private/public function, the JS-shaped if/for/while/try, and .flsh classes (extends/override/super).

Full BrightScript grammar

A lexer, recursive-descent parser, and typed AST — independent of kopytko-brightscript-parser at parse time.

SceneGraph XML grammar

A lexer/parser/AST for the template markup and generated .xml output.

kopytko-brightscript-parser remains a dependency for two narrow, non-parsing roles inside flash-theater-compiler only (validating generated .brs post-codegen, and supplying Roku's builtin-function name catalog) — never for parsing DSL source.

The BrightScript grammar

A full, self-sufficient BrightScript expression/statement grammar — used for every embedded region inside DSL source, and for BrightScript-level scope resolution.

example.ts
import { tokenizeBrightScript, parseBrightScript, wrapBrightScriptNode } from 'flash-parser';

// A full, self-sufficient BrightScript grammar — vendored and adapted from
// kopytko-brightscript-parser, but parsed independently, not delegated to
// it at parse time. Used for every embedded expression/statement region
// inside DSL source.
const { root, diagnostics } = parseBrightScript(`
function add(a as Integer, b as Integer) as Integer
  return a + b
end function
`);

const file = wrapBrightScriptNode(root);
console.log(diagnostics.length); // 0

The SceneGraph XML grammar

A dedicated XML lexer/parser/AST for template markup and generated output — not a general-purpose XML library, just enough SceneGraph XML to round-trip losslessly.

example.ts
import { parseSceneGraphXml } from 'flash-parser';

// A SceneGraph XML lexer/parser/AST — used for the template markup and
// for parsing generated .xml output back for validation. Returns the
// typed root XmlElement directly (undefined if the document has none).
const root = parseSceneGraphXml(`
<component name="Widget" extends="Group">
  <children>
    <Label id="label" text="hi" />
  </children>
</component>
`);

console.log(root.tagName); // 'component'
console.log(root.attributes.map((a) => `${a.name}=${a.value}`));
// [ 'name=Widget', 'extends=Group' ]

Full API surface

Beyond the examples above, the package also exports:

DSL AST nodes

ThrFile/FieldDeclaration/DerivedDeclaration/IfStatement/ClassDeclaration/... — one typed class per grammar construct, from ast.ts.

BrightScript AST + scopes

BsFunctionDeclaration/BsIfStatement/BsCallExpression/..., plus buildBrightScriptScopes/resolveBrightScriptName/findBrightScriptScopeAtLine.

Tokens & trivia

tokenize/TokenKind/Token/tokenFullText — the raw token stream with whitespace/comments attached, underneath the CST.

Visitor helpers

walk (a plain depth-first (node) => void callback over every CST node) and findAll (collect every node matching one SyntaxKind, wrapped into a typed AST class).

Embedded-region helpers

parseEmbeddedExpression/parseEmbeddedStatements/findTopLevelIdentifiers/findMemberAccesses/... — used by the compiler to analyze one BrightScript expression/statement region inside DSL source without reparsing the whole file.

The full, current export list is src/index.ts — every symbol there ships with its own TypeScript types.

Documentation