npm / kopytko-brightscript-parser v1.2.2

BrightScript Parser

A lossless, hand-written BrightScript lexer and recursive-descent parser. Produces a Concrete Syntax Tree that preserves every byte of the original source — whitespace, comments, and all.

Processing pipeline

Source text
Raw .brs string
Lexer
tokenize()
Token[]
with trivia
Parser
parse()
SyntaxNode (CST)
lossless tree
wrapNode()
typed AST
Scope analysis
buildScopes()

Installation

npm install kopytko-brightscript-parser

Basic usage

The three main entry points: tokenize, parse, and buildScopes.

import { tokenize, parse, buildScopes } from 'kopytko-brightscript-parser';

const source = `
function add(a as Integer, b as Integer) as Integer
  return a + b
end function
`;

// 1. Tokenize — get the raw token stream
const tokens = tokenize(source);
console.log(tokens.length);          // 20 tokens
console.log(tokens[0].kind);         // 'Function'
console.log(tokens[0].text);         // 'function'

// 2. Parse — get the full CST + diagnostics
const { root, diagnostics } = parse(source);
console.log(diagnostics.length);     // 0 (no errors)

// 3. Scope analysis
const scope = buildScopes(root);
const addDecl = scope.declarations.get('add');
console.log(addDecl?.kind);          // 'function'

Live token visualizer

Edit the BrightScript source below to see how the lexer splits it into tokens in real time. Each token kind is color-coded by category.

Live Tokenizer75 tokens
Input — edit to see live tokenization
Token-colored source
' BrightScript sample — edit to explore
function greet(name as String) as String
  if name = "" or name = invalid then
    return "Hello, World!"
  end if
  return "Hello, " + name + "!"
end function

sub main()
  colors = ["red", "green", "blue"]
  for i = 0 to colors.Count() - 1
    print colors[i]  ' current color
  end for
  m.count = 0
  m.count++
  result = greet("Roku")
end sub
Keyword(16)
String literal(8)
Number literal(3)
Bool / Invalid(1)
Identifier(18)
Type name(2)
Operator(10)
Punctuation(17)
Comment(2)

Token kind reference

Literals

IntegerLiteralLongIntegerLiteralFloatLiteralDoubleLiteralStringLiteral

Identifiers

Identifier

Type names

TypeName — parser-produced; any token after as

The only parser-produced token kind. The lexer emits type names as Identifier tokens; the parser re-classifies whichever token follows as to TypeName in the CST. Use isTypeKeyword(kind) to detect them.

Keywords

AndAsBoxCatchContinueCreateObjectDimEachElseElseIfEndEndForEndFunctionEndIfEndSubEndTryEndWhileEvalExitExitWhileFalseForFunctionGetGlobalAAGetLastRunCompileErrorGetLastRunRunTimeErrorGotoIfInInvalidLetLineNumModNextNotObjFunOrPosPrintReturnRunStepStopSubTabThenThrowToTrueTryTypeWhile

Operators

PlusMinusStarSlashBackslashCaretEqualLessGreaterLessGreaterLessEqualGreaterEqualLeftShiftRightShiftPlusEqualMinusEqualStarEqualSlashEqualBackslashEqualLeftShiftEqualRightShiftEqualPlusPlusMinusMinusQuestionDotQuestionBracketQuestionParenQuestionAt

Punctuation

LeftParenRightParenLeftBracketRightBracketLeftBraceRightBraceDotCommaColonAt

Preprocessor

HashIfHashElseIfHashElseHashEndIfHashConstHashError

Typed AST & visitor

wrapNode() converts a raw CST node into a typed wrapper. walk() traverses the tree calling your visitor methods.

import { parse, wrapNode, SourceFile, FunctionDeclaration, walk } from 'kopytko-brightscript-parser';

const { root } = parse(`
function greet(name as String) as String
  return "Hello, " + name
end function
`);

const file = wrapNode(root) as SourceFile;

// Walk the AST with a visitor
walk(file, {
  visitFunctionDeclaration(node: FunctionDeclaration) {
    console.log('Function:', node.name?.text);
    console.log('Params:', node.parameterList?.parameters.map(p => p.name?.text));
    console.log('ReturnType:', node.returnTypeClause?.typeName?.text);
  }
});
// Function: greet
// Params: ['name']
// ReturnType: String

Scope analysis

buildScopes(root) walks the AST and builds a hierarchical scope tree. resolve(name, scope) does case-insensitive lookup up the scope chain.

import { parse, buildScopes, resolve, findScopeAtLine } from 'kopytko-brightscript-parser';

const source = `
function outer() as Void
  x = 10
  inner = function(y as Integer) as Integer
    return x + y
  end function
end function
`;

const { root } = parse(source);
const fileScope = buildScopes(root);

// outer() scope
const outerScope = fileScope.children[0];
console.log(outerScope.ownerName);   // 'outer'
console.log([...outerScope.declarations.keys()]);
// ['x', 'inner']

// Resolve a name from the inner scope
const innerScope = outerScope.children[0];
const xDecl = resolve('x', innerScope);
console.log(xDecl?.kind);            // 'variable' (found in parent scope)

// Find innermost scope at a given line (0-based)
const scope = findScopeAtLine(fileScope, 4);
console.log(scope.ownerName);        // '<anonymous>'

Built-in catalogs

BRIGHTSCRIPT_BUILTINS
59 functions

Complete BrightScript standard library with signatures, return types, and descriptions.

BRIGHTSCRIPT_COMPONENTS
63 components

All ro* component interfaces with method signatures, firmware since, and deprecation flags.

SG_NODES
88 nodes

Full SceneGraph node catalog including interfaces, fields, and parent hierarchy.

Catalog lookups
findBuiltin(name) → BuiltinFunction | undefined

Look up a BrightScript built-in function by name (case-insensitive). Returns signature, return type, and documentation.

findComponent(name) → Component | undefined

Look up a ro* component (roArray, roSGNode, roUrlTransfer, …) by name.

getComponentMethods(name) → Method[]

Get all interface methods of a ro* component, including firmware version and deprecation flags.

findMethodInterface(component, method) → MethodInterface | undefined

Find a specific method's full interface definition including parameter types and return type.

Identifier casing
applyCasing(text, option) → string

Apply a casing option (lower-case, pascal-case, camel-case, …) to an identifier string.

applyCasingWithOverrides(text, config, kind) → string

Apply casing according to a CasingConfig, checking per-identifier exact overrides first.

resolveKeywordCasing(text, config) → string

Resolve the correct casing for a keyword (if, function, …) from a CasingConfig.

CasingConfig → interface

Configuration interface for all identifier casing dimensions (keyword, builtin, type, literal, logicOperator, …).

Type inference & analysis
inferTypesFromAst(root) → TypeMap

Walk the AST and build a variable→type map. Used by the extension for member completion after CreateObject().

buildCallGraph(root) → CallGraph

Build a function call graph from the AST: who calls whom, with argument counts.

getSymbolInfo(name, root) → SymbolInfo | undefined

Get rich hover/definition data for a symbol — works for both built-ins and user functions.

inferNumericLiteralType(text) → 'Integer' | 'LongInteger' | 'Float' | 'Double'

Infer the precise BrightScript numeric type from a literal's text (e.g. "42" → Integer, "3.14" → Float, "1%" → Integer).

isNumericLiteral(kind) → boolean

Returns true if the TokenKind is any numeric literal variant (Integer, LongInteger, Float, Double).

Position & cursor
findNodeAtPosition(root, line, col) → SyntaxNode | undefined

Find the deepest CST node that contains the given 0-based line and column. Used by hover, completion, and go-to-definition providers.

findTokenAtPosition(root, line, col) → Token | undefined

Find the token at the given 0-based line and column, including tokens with trivia.

Glob & XML utilities
matchesGlob(str, pattern) → boolean

Test a path string against a glob pattern (supports ** wildcards).

findMatchingGlob(globs, str) → string | undefined

Return the first glob pattern from an array that matches the given string.

parseXmlScriptUris(xml) → string[]

Extract all <script uri="…"> URIs from a SceneGraph component XML string.

parseXmlInterface(xml) → { fields, functions }

Parse the <interface> block from component XML, returning field and function definitions.

parseXmlExtends(xml) → string | undefined

Get the extends attribute value from a <component> XML string.

Keyword & type utilities
KEYWORD_MAP → Map<string, TokenKind>

Map from lowercase keyword text to TokenKind (e.g. "if" → TokenKind.If, "endwhile" → TokenKind.EndWhile).

isKeyword(kind) → boolean

Returns true if the TokenKind is a reserved BrightScript keyword (produced by the lexer).

isTypeKeyword(kind) → boolean

Returns true if the TokenKind is TypeName — the parser-produced kind for any token that follows an as keyword in a type annotation position.