@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
946 lines • 35.4 kB
JavaScript
/**
* @fileoverview OrdoJS Parser - Refactored modular implementation
* @author OrdoJS Framework Team
*/
import { ExpressionType, LifecycleType, StatementType, SyntaxError, TokenType } from '../types/index.js';
/**
* Expression precedence levels for operator precedence parsing
*/
var Precedence;
(function (Precedence) {
Precedence[Precedence["NONE"] = 0] = "NONE";
Precedence[Precedence["ASSIGNMENT"] = 1] = "ASSIGNMENT";
Precedence[Precedence["OR"] = 2] = "OR";
Precedence[Precedence["AND"] = 3] = "AND";
Precedence[Precedence["EQUALITY"] = 4] = "EQUALITY";
Precedence[Precedence["COMPARISON"] = 5] = "COMPARISON";
Precedence[Precedence["TERM"] = 6] = "TERM";
Precedence[Precedence["FACTOR"] = 7] = "FACTOR";
Precedence[Precedence["UNARY"] = 8] = "UNARY";
Precedence[Precedence["CALL"] = 9] = "CALL";
Precedence[Precedence["PRIMARY"] = 10] = "PRIMARY";
})(Precedence || (Precedence = {}));
/**
* Enhanced OrdoJS Parser with modular architecture
*/
export class OrdoJSParser {
state;
options;
parseRules;
constructor(tokenStream, options = {}, filename = 'unknown') {
this.options = {
allowRecovery: false,
maxErrors: 10,
strictMode: true,
generateSourceMaps: true,
nodeProcessors: [],
experimentalFeatures: [],
...options
};
this.state = {
tokenStream,
current: 0,
filename,
errors: [],
warnings: [],
context: {
blockContext: 'component',
symbolTable: new Map(),
scopeDepth: 0,
options: this.options
}
};
this.parseRules = this.initializeParseRules();
}
/**
* Parse token stream into Component AST
*/
parse() {
try {
const component = this.parseComponent();
if (this.state.errors.length > 0 && !this.options.allowRecovery) {
throw this.state.errors[0];
}
// Process AST through registered processors
this.processAST(component);
return {
component,
dependencies: this.extractDependencies(component),
exports: this.extractExports(component),
sourceMap: this.options.generateSourceMaps ? this.generateSourceMap() : {
version: 3,
sources: [this.state.filename],
names: [],
mappings: '',
sourcesContent: []
}
};
}
catch (error) {
if (error instanceof SyntaxError) {
throw error;
}
throw new SyntaxError(`Parser error: ${error instanceof Error ? error.message : 'Unknown error'}`, this.getCurrentPosition(), ['valid component syntax'], this.peek().value, this.state.filename);
}
}
/**
* Get all errors encountered during parsing
*/
getErrors() {
return [...this.state.errors];
}
/**
* Get all warnings encountered during parsing
*/
getWarnings() {
return [...this.state.warnings];
}
/**
* Add custom AST node processor
*/
addNodeProcessor(processor) {
this.options.nodeProcessors.push(processor);
}
/**
* Remove AST node processor by name
*/
removeNodeProcessor(name) {
const index = this.options.nodeProcessors.findIndex(p => p.name === name);
if (index >= 0) {
this.options.nodeProcessors.splice(index, 1);
return true;
}
return false;
}
initializeParseRules() {
const rules = new Map();
// Literals
rules.set(TokenType.BOOLEAN, { prefix: this.parseLiteral.bind(this), precedence: Precedence.NONE });
rules.set(TokenType.NUMBER, { prefix: this.parseLiteral.bind(this), precedence: Precedence.NONE });
rules.set(TokenType.STRING, { prefix: this.parseLiteral.bind(this), precedence: Precedence.NONE });
rules.set(TokenType.IDENTIFIER, { prefix: this.parseIdentifier.bind(this), precedence: Precedence.NONE });
// Grouping
rules.set(TokenType.LEFT_PAREN, { prefix: this.parseGrouping.bind(this), precedence: Precedence.NONE });
// Unary operators
rules.set(TokenType.LOGICAL_NOT, { prefix: this.parseUnary.bind(this), precedence: Precedence.NONE });
rules.set(TokenType.MINUS, { prefix: this.parseUnary.bind(this), infix: this.parseBinary.bind(this), precedence: Precedence.TERM });
// Binary operators
rules.set(TokenType.PLUS, { infix: this.parseBinary.bind(this), precedence: Precedence.TERM });
rules.set(TokenType.MULTIPLY, { infix: this.parseBinary.bind(this), precedence: Precedence.FACTOR });
rules.set(TokenType.DIVIDE, { infix: this.parseBinary.bind(this), precedence: Precedence.FACTOR });
rules.set(TokenType.MODULO, { infix: this.parseBinary.bind(this), precedence: Precedence.FACTOR });
// Comparison operators
rules.set(TokenType.EQUALS, { infix: this.parseBinary.bind(this), precedence: Precedence.EQUALITY });
rules.set(TokenType.NOT_EQUALS, { infix: this.parseBinary.bind(this), precedence: Precedence.EQUALITY });
rules.set(TokenType.LESS_THAN, { infix: this.parseBinary.bind(this), precedence: Precedence.COMPARISON });
rules.set(TokenType.GREATER_THAN, { infix: this.parseBinary.bind(this), precedence: Precedence.COMPARISON });
rules.set(TokenType.LESS_EQUAL, { infix: this.parseBinary.bind(this), precedence: Precedence.COMPARISON });
rules.set(TokenType.GREATER_EQUAL, { infix: this.parseBinary.bind(this), precedence: Precedence.COMPARISON });
// Logical operators
rules.set(TokenType.LOGICAL_AND, { infix: this.parseBinary.bind(this), precedence: Precedence.AND });
rules.set(TokenType.LOGICAL_OR, { infix: this.parseBinary.bind(this), precedence: Precedence.OR });
// Assignment
rules.set(TokenType.ASSIGN, { infix: this.parseAssignment.bind(this), precedence: Precedence.ASSIGNMENT });
// Call and member access
rules.set(TokenType.LEFT_PAREN, { infix: this.parseCall.bind(this), precedence: Precedence.CALL });
rules.set(TokenType.DOT, { infix: this.parseMember.bind(this), precedence: Precedence.CALL });
return rules;
}
parseComponent() {
const start = this.getCurrentPosition();
this.state.context.blockContext = 'component';
// Expect 'component' keyword
if (!this.match(TokenType.COMPONENT)) {
this.throwError("Expected 'component' keyword", ['component']);
}
// Parse component name
const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected component name');
const name = nameToken.value;
// Validate component name
if (!this.isValidComponentName(name)) {
this.throwError('Component name must start with uppercase letter and contain only alphanumeric characters', []);
}
// Add component to symbol table
this.addSymbol(name, 'function', undefined, this.getCurrentPosition());
// Parse optional props
const props = this.parseProps();
// Expect component body
this.consume(TokenType.LEFT_BRACE, "Expected '{' after component declaration");
this.enterScope();
// Parse component blocks
let clientBlock;
let serverBlock;
let markupBlock;
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
if (this.match(TokenType.CLIENT)) {
if (clientBlock) {
this.throwError('Duplicate client block', []);
}
clientBlock = this.parseClientBlock();
}
else if (this.match(TokenType.SERVER)) {
if (serverBlock) {
this.throwError('Duplicate server block', []);
}
serverBlock = this.parseServerBlock();
}
else if (this.match(TokenType.MARKUP)) {
if (markupBlock) {
this.throwError('Duplicate markup block', []);
}
markupBlock = this.parseMarkupBlock();
}
else {
this.throwError('Expected client, server, or markup block', ['client', 'server', 'markup']);
}
}
this.exitScope();
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after component body");
// Markup block is required
if (!markupBlock) {
this.throwError('Component must have a markup block', ['markup']);
}
const end = this.getCurrentPosition();
const component = {
type: 'Component',
name,
props,
clientBlock,
serverBlock,
markupBlock: markupBlock,
range: { start, end }
};
this.state.context.component = component;
return component;
}
parseProps() {
const props = [];
if (!this.check(TokenType.LEFT_PAREN)) {
return props;
}
this.advance(); // consume '('
while (!this.check(TokenType.RIGHT_PAREN) && !this.isAtEnd()) {
const prop = this.parseProp();
props.push(prop);
// Add prop to symbol table
this.addSymbol(prop.name, 'prop', prop.dataType, prop.range.start);
if (!this.match(TokenType.COMMA)) {
break;
}
}
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after props");
return props;
}
parseProp() {
const start = this.getCurrentPosition();
const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected prop name');
this.consume(TokenType.COLON, "Expected ':' after prop name");
const dataType = this.parseTypeAnnotation();
let defaultValue;
let isRequired = true;
if (this.match(TokenType.ASSIGN)) {
isRequired = false;
defaultValue = this.parseExpression();
}
const end = this.getCurrentPosition();
return {
type: 'PropDefinition',
name: nameToken.value,
dataType,
defaultValue,
isRequired,
range: { start, end }
};
}
parseTypeAnnotation() {
const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected type name');
let isArray = false;
let isOptional = false;
const genericTypes = [];
// Handle array types
if (this.match(TokenType.LEFT_BRACKET)) {
this.consume(TokenType.RIGHT_BRACKET, "Expected ']' after '['");
isArray = true;
}
// Handle optional types
if (this.match(TokenType.QUESTION)) {
isOptional = true;
}
// Handle generic types (simplified)
if (this.match(TokenType.LESS_THAN)) {
do {
genericTypes.push(this.parseTypeAnnotation());
} while (this.match(TokenType.COMMA));
this.consume(TokenType.GREATER_THAN, "Expected '>' after generic types");
}
return {
name: nameToken.value,
isArray,
isOptional,
genericTypes
};
}
parseClientBlock() {
const start = this.getCurrentPosition();
this.state.context.blockContext = 'client';
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'client'");
this.enterScope();
const reactiveVariables = [];
const computedValues = [];
const eventHandlers = [];
const functions = [];
const lifecycle = [];
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
if (this.check(TokenType.LET) || this.check(TokenType.CONST)) {
reactiveVariables.push(this.parseReactiveVariable());
}
else if (this.checkLifecycleHook()) {
lifecycle.push(this.parseLifecycleHook());
}
else if (this.check(TokenType.IDENTIFIER)) {
functions.push(this.parseFunction());
}
else {
this.throwError('Expected variable declaration, function, or lifecycle hook', ['let', 'const', 'function', 'onMount', 'onUnmount', 'onUpdate']);
}
}
this.exitScope();
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after client block");
const end = this.getCurrentPosition();
return {
type: 'ClientBlock',
reactiveVariables,
computedValues,
eventHandlers,
functions,
lifecycle,
range: { start, end }
};
}
parseServerBlock() {
const start = this.getCurrentPosition();
this.state.context.blockContext = 'server';
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'server'");
this.enterScope();
const functions = [];
const middleware = [];
const dataFetchers = [];
const imports = [];
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
if (this.check(TokenType.PUBLIC) || this.check(TokenType.IDENTIFIER)) {
functions.push(this.parseServerFunction());
}
else {
this.throwError('Expected function declaration', ['public', 'function']);
}
}
this.exitScope();
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after server block");
const end = this.getCurrentPosition();
return {
type: 'ServerBlock',
functions,
middleware,
dataFetchers,
imports,
range: { start, end }
};
}
parseMarkupBlock() {
const start = this.getCurrentPosition();
this.state.context.blockContext = 'markup';
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'markup'");
const elements = [];
const textNodes = [];
const interpolations = [];
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
if (this.check(TokenType.HTML_TAG_OPEN)) {
elements.push(this.parseHTMLElement());
}
else if (this.check(TokenType.INTERPOLATION_START)) {
interpolations.push(this.parseInterpolation());
}
else if (this.check(TokenType.HTML_TEXT)) {
textNodes.push(this.parseTextNode());
}
else {
// Skip unknown tokens in markup context
this.advance();
}
}
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after markup block");
const end = this.getCurrentPosition();
return {
type: 'MarkupBlock',
elements,
textNodes,
interpolations,
range: { start, end }
};
}
parseReactiveVariable() {
const start = this.getCurrentPosition();
const isConst = this.match(TokenType.CONST);
if (!isConst) {
this.consume(TokenType.LET, "Expected 'let' or 'const'");
}
const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected variable name');
// Check for duplicate declaration
if (this.hasSymbolInCurrentScope(nameToken.value)) {
this.throwError(`Variable '${nameToken.value}' is already declared in this scope`, []);
}
let dataType = { name: 'any', isArray: false, isOptional: false, genericTypes: [] };
if (this.match(TokenType.COLON)) {
dataType = this.parseTypeAnnotation();
}
this.consume(TokenType.ASSIGN, "Expected '=' after variable declaration");
const initialValue = this.parseExpression();
this.match(TokenType.SEMICOLON); // Optional semicolon
// Add variable to symbol table
this.addSymbol(nameToken.value, 'variable', dataType, start, isConst);
const end = this.getCurrentPosition();
return {
type: 'ReactiveVariable',
name: nameToken.value,
initialValue,
dataType,
isConst,
range: { start, end }
};
}
parseFunction() {
const start = this.getCurrentPosition();
const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected function name');
// Check for duplicate declaration
if (this.hasSymbolInCurrentScope(nameToken.value)) {
this.throwError(`Function '${nameToken.value}' is already declared in this scope`, []);
}
this.consume(TokenType.LEFT_PAREN, "Expected '(' after function name");
this.enterScope();
const parameters = this.parseParameters();
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters");
this.consume(TokenType.COLON, "Expected ':' after parameters");
const returnType = this.parseTypeAnnotation();
this.consume(TokenType.LEFT_BRACE, "Expected '{' after function signature");
const body = this.parseStatements();
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after function body");
this.exitScope();
// Add function to symbol table
this.addSymbol(nameToken.value, 'function', returnType, start);
const end = this.getCurrentPosition();
return {
type: 'Function',
name: nameToken.value,
parameters,
body,
returnType,
isAsync: false,
range: { start, end }
};
}
parseServerFunction() {
const start = this.getCurrentPosition();
const isPublic = this.match(TokenType.PUBLIC);
const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected function name');
// Check for duplicate declaration
if (this.hasSymbolInCurrentScope(nameToken.value)) {
this.throwError(`Function '${nameToken.value}' is already declared in this scope`, []);
}
this.consume(TokenType.LEFT_PAREN, "Expected '(' after function name");
this.enterScope();
const parameters = this.parseParameters();
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters");
this.consume(TokenType.COLON, "Expected ':' after parameters");
const returnType = this.parseTypeAnnotation();
this.consume(TokenType.LEFT_BRACE, "Expected '{' after function signature");
const body = this.parseStatements();
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after function body");
this.exitScope();
// Add function to symbol table
this.addSymbol(nameToken.value, 'function', returnType, start, false, isPublic);
const end = this.getCurrentPosition();
return {
type: 'ServerFunction',
name: nameToken.value,
parameters,
body,
returnType,
isAsync: false,
isPublic,
middleware: [],
permissions: [],
range: { start, end }
};
}
parseLifecycleHook() {
const start = this.getCurrentPosition();
const hookName = this.advance().value;
let hookType;
switch (hookName) {
case 'onMount':
hookType = LifecycleType.ON_MOUNT;
break;
case 'onUnmount':
hookType = LifecycleType.ON_UNMOUNT;
break;
case 'onUpdate':
hookType = LifecycleType.ON_UPDATE;
break;
case 'beforeUpdate':
hookType = LifecycleType.BEFORE_UPDATE;
break;
case 'afterUpdate':
hookType = LifecycleType.AFTER_UPDATE;
break;
default:
this.throwError(`Unknown lifecycle hook: ${hookName}`, ['onMount', 'onUnmount', 'onUpdate', 'beforeUpdate', 'afterUpdate']);
hookType = LifecycleType.ON_MOUNT;
}
this.consume(TokenType.LEFT_PAREN, "Expected '(' after lifecycle hook");
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after lifecycle hook");
this.consume(TokenType.LEFT_BRACE, "Expected '{' after lifecycle hook");
this.enterScope();
const handler = this.parseStatements();
this.exitScope();
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after lifecycle hook body");
const end = this.getCurrentPosition();
return {
type: 'LifecycleHook',
hookType,
handler,
range: { start, end }
};
}
parseParameters() {
const parameters = [];
while (!this.check(TokenType.RIGHT_PAREN) && !this.isAtEnd()) {
const param = this.parseParameter();
parameters.push(param);
// Add parameter to symbol table
this.addSymbol(param.name, 'parameter', param.dataType, param.range.start);
if (!this.match(TokenType.COMMA)) {
break;
}
}
return parameters;
}
parseParameter() {
const start = this.getCurrentPosition();
const nameToken = this.consume(TokenType.IDENTIFIER, 'Expected parameter name');
this.consume(TokenType.COLON, "Expected ':' after parameter name");
const dataType = this.parseTypeAnnotation();
let defaultValue;
let isOptional = dataType.isOptional;
if (this.match(TokenType.ASSIGN)) {
isOptional = true;
defaultValue = this.parseExpression();
}
const end = this.getCurrentPosition();
return {
type: 'Parameter',
name: nameToken.value,
dataType,
defaultValue,
isOptional,
range: { start, end }
};
}
parseStatements() {
const statements = [];
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
statements.push(this.parseStatement());
}
return statements;
}
parseStatement() {
const start = this.getCurrentPosition();
// For now, treat everything as expression statements
const expression = this.parseExpression();
this.match(TokenType.SEMICOLON); // Optional semicolon
const end = this.getCurrentPosition();
return {
type: 'Statement',
statementType: StatementType.EXPRESSION,
expression,
range: { start, end }
};
}
parseExpression() {
return this.parsePrecedence(Precedence.ASSIGNMENT);
}
parsePrecedence(precedence) {
const prefixRule = this.parseRules.get(this.peek().type)?.prefix;
if (!prefixRule) {
this.throwError('Expected expression', ['identifier', 'number', 'string', 'boolean', '(']);
// Return dummy expression for error recovery
return this.createDummyExpression();
}
let left = prefixRule(this);
while (precedence <= this.getPrecedence()) {
const infixRule = this.parseRules.get(this.peek().type)?.infix;
if (!infixRule)
break;
left = infixRule(this, left);
}
return left;
}
getPrecedence() {
const rule = this.parseRules.get(this.peek().type);
return rule?.precedence || Precedence.NONE;
}
// Expression parsing methods
parseLiteral() {
const token = this.previous();
let value = token.value;
if (token.type === TokenType.BOOLEAN) {
value = token.value === 'true';
}
else if (token.type === TokenType.NUMBER) {
value = parseFloat(token.value);
}
return {
type: 'Expression',
expressionType: ExpressionType.LITERAL,
value,
range: token.range
};
}
parseIdentifier() {
const token = this.previous();
// Check if identifier is declared
if (this.options.strictMode && !this.hasSymbol(token.value)) {
this.addWarning(`Undefined variable: '${token.value}'`, token.range.start);
}
return {
type: 'Expression',
expressionType: ExpressionType.IDENTIFIER,
identifier: token.value,
range: token.range
};
}
parseGrouping() {
const expr = this.parseExpression();
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after expression");
return expr;
}
parseUnary() {
const operator = this.previous();
const right = this.parsePrecedence(Precedence.UNARY);
return {
type: 'Expression',
expressionType: ExpressionType.UNARY,
operator: operator.value,
right,
range: { start: operator.range.start, end: right.range.end }
};
}
parseBinary(parser, left) {
const operator = this.previous();
const rule = this.parseRules.get(operator.type);
const right = this.parsePrecedence((rule?.precedence || Precedence.NONE) + 1);
return {
type: 'Expression',
expressionType: ExpressionType.BINARY,
operator: operator.value,
left,
right,
range: { start: left.range.start, end: right.range.end }
};
}
parseAssignment(parser, left) {
const operator = this.previous();
const right = this.parseExpression();
return {
type: 'Expression',
expressionType: ExpressionType.ASSIGNMENT,
operator: operator.value,
left,
right,
range: { start: left.range.start, end: right.range.end }
};
}
parseCall(parser, left) {
const args = [];
if (!this.check(TokenType.RIGHT_PAREN)) {
do {
args.push(this.parseExpression());
} while (this.match(TokenType.COMMA));
}
const paren = this.consume(TokenType.RIGHT_PAREN, "Expected ')' after arguments");
return {
type: 'Expression',
expressionType: ExpressionType.CALL,
callee: left,
arguments: args,
range: { start: left.range.start, end: paren.range.end }
};
}
parseMember(parser, left) {
const name = this.consume(TokenType.IDENTIFIER, "Expected property name after '.'");
return {
type: 'Expression',
expressionType: ExpressionType.MEMBER,
object: left,
property: {
type: 'Expression',
expressionType: ExpressionType.IDENTIFIER,
identifier: name.value,
range: name.range
},
range: { start: left.range.start, end: name.range.end }
};
}
parseHTMLElement() {
const start = this.getCurrentPosition();
// Simplified HTML parsing - would need much more sophisticated implementation
const tagName = 'div'; // Placeholder
const attributes = [];
const children = [];
const end = this.getCurrentPosition();
return {
type: 'HTMLElement',
tagName,
attributes,
children,
isSelfClosing: false,
isVoidElement: false,
range: { start, end }
};
}
parseInterpolation() {
const start = this.getCurrentPosition();
this.consume(TokenType.INTERPOLATION_START, "Expected '{' for interpolation");
const expression = this.parseExpression();
this.consume(TokenType.INTERPOLATION_END, "Expected '}' after interpolation");
const end = this.getCurrentPosition();
return {
type: 'Interpolation',
expression,
range: { start, end }
};
}
parseTextNode() {
const start = this.getCurrentPosition();
const content = this.consume(TokenType.HTML_TEXT, 'Expected text content').value;
const end = this.getCurrentPosition();
return {
type: 'Text',
content,
range: { start, end }
};
}
// Symbol table management
enterScope() {
this.state.context.scopeDepth++;
}
exitScope() {
// Remove symbols from current scope
for (const [name, symbol] of this.state.context.symbolTable) {
if (symbol.scope === this.state.context.scopeDepth) {
this.state.context.symbolTable.delete(name);
}
}
this.state.context.scopeDepth--;
}
addSymbol(name, type, dataType, position, isConst = false, isPublic = false) {
this.state.context.symbolTable.set(name, {
name,
type,
dataType,
scope: this.state.context.scopeDepth,
position,
isConst,
isPublic
});
}
hasSymbol(name) {
return this.state.context.symbolTable.has(name);
}
hasSymbolInCurrentScope(name) {
const symbol = this.state.context.symbolTable.get(name);
return symbol?.scope === this.state.context.scopeDepth;
}
getSymbol(name) {
return this.state.context.symbolTable.get(name);
}
// AST processing
processAST(component) {
if (this.options.nodeProcessors.length === 0)
return;
this.processNode(component);
}
processNode(node) {
for (const processor of this.options.nodeProcessors) {
if (processor.handles.includes(node.type)) {
node = processor.process(node, this.state.context);
}
}
// Recursively process child nodes
if (node.children) {
for (const child of node.children) {
this.processNode(child);
}
}
// Process specific node types
if (node.clientBlock)
this.processNode(node.clientBlock);
if (node.serverBlock)
this.processNode(node.serverBlock);
if (node.markupBlock)
this.processNode(node.markupBlock);
if (node.functions) {
for (const func of node.functions) {
this.processNode(func);
}
}
if (node.reactiveVariables) {
for (const variable of node.reactiveVariables) {
this.processNode(variable);
}
}
}
// Utility methods
checkLifecycleHook() {
const value = this.peek().value;
return ['onMount', 'onUnmount', 'onUpdate', 'beforeUpdate', 'afterUpdate'].includes(value);
}
isValidComponentName(name) {
return /^[A-Z][a-zA-Z0-9]*$/.test(name);
}
createDummyExpression() {
return {
type: 'Expression',
expressionType: ExpressionType.LITERAL,
value: null,
range: { start: this.getCurrentPosition(), end: this.getCurrentPosition() }
};
}
extractDependencies(component) {
// Extract import dependencies from the component
const dependencies = [];
// Implementation would analyze the AST for imports
return dependencies;
}
extractExports(component) {
// Extract exports from the component
const exports = [component.name];
// Implementation would analyze the AST for exports
return exports;
}
generateSourceMap() {
// Generate source map for debugging
return {
version: 3,
sources: [this.state.filename],
names: [],
mappings: '',
sourcesContent: []
};
}
// Token stream utilities
match(...types) {
for (const type of types) {
if (this.check(type)) {
this.advance();
return true;
}
}
return false;
}
check(type) {
if (this.isAtEnd())
return false;
return this.peek().type === type;
}
advance() {
if (!this.isAtEnd())
this.state.current++;
return this.previous();
}
isAtEnd() {
return this.peek().type === TokenType.EOF;
}
peek() {
return this.state.tokenStream.tokens[this.state.current] || this.createEOFToken();
}
previous() {
return this.state.tokenStream.tokens[this.state.current - 1] || this.createEOFToken();
}
consume(type, message) {
if (this.check(type))
return this.advance();
this.throwError(message, [type.toString()]);
// Return dummy token for error recovery
return this.createEOFToken();
}
createEOFToken() {
return {
type: TokenType.EOF,
value: '',
position: this.getCurrentPosition(),
range: { start: this.getCurrentPosition(), end: this.getCurrentPosition() }
};
}
throwError(message, expected) {
const error = new SyntaxError(message, this.getCurrentPosition(), expected, this.peek().value, this.state.filename);
if (this.options.allowRecovery && this.state.errors.length < this.options.maxErrors) {
this.state.errors.push(error);
this.synchronize();
throw error;
}
throw error;
}
addWarning(message, position) {
const warning = new SyntaxError(message, position, [], '', this.state.filename);
this.state.warnings.push(warning);
}
synchronize() {
this.advance();
while (!this.isAtEnd()) {
if (this.previous().type === TokenType.SEMICOLON)
return;
switch (this.peek().type) {
case TokenType.COMPONENT:
case TokenType.CLIENT:
case TokenType.SERVER:
case TokenType.MARKUP:
case TokenType.LET:
case TokenType.CONST:
return;
}
this.advance();
}
}
getCurrentPosition() {
return this.peek().position;
}
}
/**
* Default AST node processors
*/
export const defaultNodeProcessors = [
{
name: 'symbol-validator',
handles: ['Expression'],
process: (node, context) => {
if (node.type === 'Expression') {
const expr = node;
if (expr.expressionType === ExpressionType.IDENTIFIER && expr.identifier) {
if (!context.symbolTable.has(expr.identifier)) {
// Could emit warning about undefined variable
}
}
}
return node;
}
},
{
name: 'type-checker',
handles: ['ReactiveVariable', 'Function', 'ServerFunction'],
process: (node, context) => {
// Could perform type checking here
return node;
}
}
];
//# sourceMappingURL=parser-refactored.js.map