@rcs-lang/ast
@rcs-lang/ast
Section titled “@rcs-lang/ast”The @rcs-lang/ast package provides TypeScript type definitions for working with RCL Abstract Syntax Trees. This guide will help you understand how to use these types effectively in your projects.
Installation
Section titled “Installation”npm install @rcs-lang/astpnpm add @rcs-lang/astyarn add @rcs-lang/astbun add @rcs-lang/astBasic Usage
Section titled “Basic Usage”Importing Types
Section titled “Importing Types”// Import specific typesimport { RclFile, AgentDefinition, FlowDefinition } from '@rcs-lang/ast';
// Import all types under namespaceimport * as AST from '@rcs-lang/ast';
// Import utilitiesimport { walkAST, isMessageDefinition } from '@rcs-lang/ast';Working with AST Nodes
Section titled “Working with AST Nodes”Every AST node has a type property that identifies its kind and a range property that indicates its location in the source code.
import { ASTNode, Range } from '@rcs-lang/ast';
function printNodeInfo(node: ASTNode) { console.log(`Node type: ${node.type}`); console.log(`Location: Line ${node.range.start.line}, Column ${node.range.start.character}`);}Understanding the AST Structure
Section titled “Understanding the AST Structure”Document Hierarchy
Section titled “Document Hierarchy”RCL documents follow this structure:
RclFile├── AgentDefinition│ ├── ConfigSection (optional)│ ├── DefaultsSection (optional)│ └── FlowDefinition[] (one or more)└── MessagesSection (optional) └── MessageDefinition[] (zero or more)Example AST
Section titled “Example AST”For this RCL code:
agent CoffeeBot displayName: "Coffee Shop Assistant"
flow OrderFlow start: Welcome
on Welcome match @reply.text "coffee" -> OrderCoffee :default -> Welcome
messages Messages text Welcome "What can I get you?"The AST structure would be:
const ast: AST.RclFile = { type: 'RclFile', agent: { type: 'AgentDefinition', name: 'CoffeeBot', displayName: 'Coffee Shop Assistant', flows: [ { type: 'FlowDefinition', name: 'OrderFlow', start: 'Welcome', states: [ { type: 'StateDefinition', name: 'Welcome', transitions: [/* ... */] } ] } ] }, messages: { type: 'MessagesSection', messages: [ { type: 'TextMessage', name: 'Welcome', messageType: 'text', text: 'What can I get you?' } ] }}Working with Different Node Types
Section titled “Working with Different Node Types”Agent Definitions
Section titled “Agent Definitions”import { AgentDefinition, isAgentDefinition } from '@rcs-lang/ast';
function analyzeAgent(node: ASTNode) { if (isAgentDefinition(node)) { console.log(`Agent: ${node.name}`); console.log(`Display Name: ${node.displayName || 'Not set'}`); console.log(`Flows: ${node.flows.length}`);
// Check for configuration if (node.config) { console.log('Has configuration section'); }
// Check for defaults if (node.defaults) { console.log('Has defaults section'); } }}Flow Definitions
Section titled “Flow Definitions”import { FlowDefinition, isFlowDefinition } from '@rcs-lang/ast';
function analyzeFlow(node: ASTNode) { if (isFlowDefinition(node)) { console.log(`Flow: ${node.name}`); console.log(`Start State: ${node.start}`); console.log(`States: ${node.states.map(s => s.name).join(', ')}`); }}Message Definitions
Section titled “Message Definitions”import { MessageDefinition, isTextMessage, isRichCardMessage, isCarouselMessage} from '@rcs-lang/ast';
function analyzeMessage(message: MessageDefinition) { console.log(`Message: ${message.name}`);
if (isTextMessage(message)) { console.log(`Type: Text`); console.log(`Content: ${message.text}`);
if (message.suggestions) { console.log(`Suggestions: ${message.suggestions.length}`); } } else if (isRichCardMessage(message)) { console.log(`Type: Rich Card`); console.log(`Title: ${message.title}`); console.log(`Size: ${message.size || 'default'}`);
if (message.description) { console.log(`Description: ${message.description}`); } } else if (isCarouselMessage(message)) { console.log(`Type: Carousel`); console.log(`Title: ${message.title}`); console.log(`Cards: ${message.cards.length}`); }}AST Traversal Patterns
Section titled “AST Traversal Patterns”Walking the Entire Tree
Section titled “Walking the Entire Tree”import { walkAST } from '@rcs-lang/ast';
function findAllIdentifiers(ast: AST.RclFile): string[] { const identifiers: string[] = [];
walkAST(ast, (node) => { if (node.type === 'Identifier') { identifiers.push((node as AST.Identifier).name); } });
return identifiers;}Finding Specific Nodes
Section titled “Finding Specific Nodes”import { findNode, findAllNodes } from '@rcs-lang/ast';
// Find the first flowfunction getFirstFlow(ast: AST.RclFile): AST.FlowDefinition | null { const flowNode = findNode(ast, (node) => node.type === 'FlowDefinition'); return flowNode as AST.FlowDefinition | null;}
// Find all text messagesfunction getAllTextMessages(ast: AST.RclFile): AST.TextMessage[] { const textNodes = findAllNodes(ast, (node) => node.type === 'TextMessage'); return textNodes as AST.TextMessage[];}Common Use Cases
Section titled “Common Use Cases”Extracting Metadata
Section titled “Extracting Metadata”import { RclFile, isMessageDefinition } from '@rcs-lang/ast';
interface AgentMetadata { name: string; displayName?: string; flowCount: number; messageCount: number; messageTypes: string[];}
function extractMetadata(ast: RclFile): AgentMetadata { const messageTypes = new Set<string>(); let messageCount = 0;
walkAST(ast, (node) => { if (isMessageDefinition(node)) { messageCount++; messageTypes.add(node.messageType); } });
return { name: ast.agent.name, displayName: ast.agent.displayName, flowCount: ast.agent.flows.length, messageCount, messageTypes: Array.from(messageTypes) };}Integration with Other Packages
Section titled “Integration with Other Packages”import { RCLParser } from '@rcs-lang/parser';import { RclFile } from '@rcs-lang/ast';
async function parseAndAnalyze(source: string) { const parser = new RCLParser(); const result = await parser.parse(source);
if (result.success) { const ast: RclFile = result.data; const metadata = extractMetadata(ast); console.log('Agent metadata:', metadata); }}Type Safety Best Practices
Section titled “Type Safety Best Practices”Using Type Guards
Section titled “Using Type Guards”Always use type guards instead of casting:
// ✅ Good: Type-safe with runtime checkingfunction processNode(node: ASTNode) { if (isFlowDefinition(node)) { // TypeScript knows node is FlowDefinition console.log(node.start); }}
// ❌ Bad: Unsafe castingfunction processNodeUnsafe(node: ASTNode) { const flow = node as FlowDefinition; // Could be wrong! console.log(flow.start); // Runtime error if not a flow}Exhaustive Type Checking
Section titled “Exhaustive Type Checking”Use discriminated unions for complete type coverage:
function processMessage(message: MessageDefinition) { switch (message.messageType) { case 'text': // Handle text message break; case 'richCard': // Handle rich card break; case 'carousel': // Handle carousel break; default: // TypeScript will error if we miss a case const exhaustive: never = message.messageType; throw new Error(`Unhandled message type: ${exhaustive}`); }}API Reference
Section titled “API Reference”For detailed API documentation of all interfaces, types, and functions, see the API Reference.