@prism-lang/core
Version:
A programming language for uncertainty
1,374 lines (1,156 loc) • 83.2 kB
text/typescript
import { Token, TokenType, tokenize } from './tokenizer';
import {
Expression,
Statement,
Program,
IdentifierExpression,
NumberLiteral,
StringLiteral,
InterpolatedString,
BooleanLiteral,
NullLiteral,
UndefinedLiteral,
BinaryExpression,
UnaryExpression,
CallExpression,
TernaryExpression,
ConfidentTernaryExpression,
ArrayLiteral,
ObjectLiteral,
PropertyAccess,
OptionalChainAccess,
IndexAccess,
ConfidenceExpression,
AssignmentStatement,
AssignmentExpression,
AwaitExpression,
IfStatement,
UncertainIfStatement,
ContextStatement,
AgentDeclaration,
BlockStatement,
ExpressionStatement,
UncertainBranches,
AgentConfig,
BinaryOperator,
UnaryOperator,
LambdaExpression,
LambdaParameter,
SpreadElement,
PlaceholderExpression,
ForLoop,
ForInLoop,
WhileLoop,
DoWhileLoop,
BreakStatement,
ContinueStatement,
UncertainForLoop,
UncertainWhileLoop,
ArrayPattern,
ObjectPattern,
RestElement,
DestructuringAssignment,
ImportStatement,
ExportStatement,
ImportSpecifier,
ExportSpecifier,
FunctionDeclaration,
ReturnStatement,
VariableDeclaration,
} from './ast';
export class ParseError extends Error {
constructor(message: string, public token: Token, public sourceCode?: string) {
const errorMessage = ParseError.formatError(message, token, sourceCode);
super(errorMessage);
this.name = 'ParseError';
}
private static formatError(message: string, token: Token, sourceCode?: string): string {
let errorMsg = `ParseError at line ${token.line}, column ${token.column}: ${message}`;
if (sourceCode) {
const lines = sourceCode.split('\n');
const errorLine = lines[token.line - 1];
if (errorLine) {
errorMsg += '\n\n';
errorMsg += ` ${token.line} | ${errorLine}\n`;
errorMsg += ` ${' '.repeat(token.column)}^`;
// Add some context - show the token that caused the error
if (token.type !== TokenType.EOF) {
errorMsg += `\n\nFound: '${token.value}' (${TokenType[token.type]})`;
}
}
}
return errorMsg;
}
}
export class Parser {
private tokens: Token[];
private current: number = 0;
private sourceCode?: string;
constructor(tokens: Token[], sourceCode?: string) {
this.tokens = tokens;
this.sourceCode = sourceCode;
}
parse(): Program {
const statements: Statement[] = [];
while (!this.isAtEnd()) {
const stmt = this.statement();
if (stmt) {
statements.push(stmt);
}
}
return new Program(statements);
}
private statement(): Statement | null {
try {
if (this.match(TokenType.ASYNC)) {
// async can only be followed by function for now
this.consume(TokenType.FUNCTION, "Expected 'function' after 'async'");
return this.functionDeclaration(true);
}
if (this.match(TokenType.FUNCTION)) {
return this.functionDeclaration(false);
}
if (this.match(TokenType.RETURN)) {
return this.returnStatement();
}
if (this.match(TokenType.CONST, TokenType.LET)) {
return this.variableDeclaration();
}
if (this.match(TokenType.AGENTS)) {
return this.agentsStatement();
}
if (this.match(TokenType.UNCERTAIN)) {
// Check what follows 'uncertain'
if (this.check(TokenType.IF)) {
return this.uncertainIfStatement();
} else if (this.check(TokenType.FOR)) {
return this.uncertainForStatement();
} else if (this.check(TokenType.WHILE)) {
return this.uncertainWhileStatement();
} else {
throw new ParseError("Expected 'if', 'for', or 'while' after 'uncertain'", this.peek(), this.sourceCode);
}
}
if (this.match(TokenType.IF)) {
return this.ifStatement();
}
if (this.match(TokenType.FOR)) {
return this.forStatement();
}
if (this.match(TokenType.WHILE)) {
return this.whileStatement();
}
if (this.match(TokenType.DO)) {
return this.doWhileStatement();
}
if (this.match(TokenType.BREAK)) {
return this.breakStatement();
}
if (this.match(TokenType.CONTINUE)) {
return this.continueStatement();
}
if (this.match(TokenType.IN)) {
return this.contextStatement();
}
if (this.match(TokenType.IMPORT)) {
return this.importStatement();
}
if (this.match(TokenType.EXPORT)) {
return this.exportStatement();
}
// Check for potential destructuring patterns
if (this.check(TokenType.LEFT_BRACKET)) {
// Array destructuring pattern - parse as expression statement
return this.expressionStatement();
}
if (this.check(TokenType.LEFT_BRACE)) {
// Look ahead to determine if this is a block or object literal
const checkpoint = this.current;
this.advance(); // consume {
let isObjectOrDestructuring = false;
// Check various patterns that indicate object literal or destructuring
if (this.check(TokenType.RIGHT_BRACE)) {
// Empty object {}
isObjectOrDestructuring = true;
} else if (this.check(TokenType.SPREAD)) {
// Spread in object {...x}
isObjectOrDestructuring = true;
} else if (this.check(TokenType.IDENTIFIER)) {
// Look ahead one more token after identifier
this.advance();
if (this.check(TokenType.COMMA) || this.check(TokenType.COLON) ||
this.check(TokenType.RIGHT_BRACE)) {
// Object property syntax
isObjectOrDestructuring = true;
} else if (this.check(TokenType.CONFIDENCE_ARROW)) {
// Confidence arrow after identifier in {} context
// This is likely a destructuring pattern
isObjectOrDestructuring = true;
} else if (this.check(TokenType.EQUAL)) {
// Could be destructuring with default value {x = default} or assignment {x = y}
// Look further ahead to distinguish
const checkPoint2 = this.current;
this.advance(); // skip =
// Skip the default value expression
let depth = 0;
while (!this.isAtEnd() && (depth > 0 || (!this.check(TokenType.COMMA) &&
!this.check(TokenType.RIGHT_BRACE)))) {
if (this.check(TokenType.LEFT_PAREN) || this.check(TokenType.LEFT_BRACKET) ||
this.check(TokenType.LEFT_BRACE)) {
depth++;
} else if (this.check(TokenType.RIGHT_PAREN) || this.check(TokenType.RIGHT_BRACKET) ||
this.check(TokenType.RIGHT_BRACE)) {
depth--;
}
this.advance();
}
// If followed by comma or }, it's likely object destructuring with default
if (this.check(TokenType.COMMA) || this.check(TokenType.RIGHT_BRACE)) {
// Check if the closing } is followed by =
if (this.check(TokenType.RIGHT_BRACE)) {
const checkPoint3 = this.current;
this.advance(); // skip }
if (this.check(TokenType.EQUAL)) {
isObjectOrDestructuring = true;
}
this.current = checkPoint3;
} else {
// Has comma, likely part of destructuring pattern
isObjectOrDestructuring = true;
}
}
this.current = checkPoint2;
}
}
// Restore position
this.current = checkpoint;
if (isObjectOrDestructuring) {
// Parse as expression statement (which will handle both object literals and destructuring)
return this.expressionStatement();
} else {
// Parse as block statement
this.advance(); // consume {
return this.blockStatement();
}
}
return this.expressionStatement();
} catch (error) {
this.synchronize();
throw error;
}
}
private agentsStatement(): Statement {
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'agents'");
const statements: Statement[] = [];
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
const agentDecl = this.agentDeclaration();
statements.push(agentDecl);
}
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after agents block");
return new BlockStatement(statements);
}
private agentDeclaration(): AgentDeclaration {
const name = this.consume(TokenType.IDENTIFIER, "Expected agent name").value;
this.consume(TokenType.COLON, "Expected ':' after agent name");
this.consume(TokenType.AGENT, "Expected 'Agent' keyword");
const config: AgentConfig = {};
if (this.match(TokenType.LEFT_BRACE)) {
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
const key = this.consume(TokenType.IDENTIFIER, "Expected config key").value;
this.consume(TokenType.COLON, "Expected ':' after config key");
if (key === 'confidence') {
const value = this.consume(TokenType.NUMBER, "Expected number for confidence").value;
config.confidence = parseFloat(value);
} else if (key === 'role') {
const value = this.consume(TokenType.STRING, "Expected string for role").value;
config.role = value;
}
if (this.check(TokenType.COMMA)) {
this.advance();
}
}
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after agent config");
}
return new AgentDeclaration(name, config);
}
private uncertainIfStatement(): UncertainIfStatement {
this.consume(TokenType.IF, "Expected 'if' after 'uncertain'");
this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'uncertain if'");
const condition = this.expression();
const threshold = 0.5; // default threshold, actual evaluation happens at runtime
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after condition");
this.consume(TokenType.LEFT_BRACE, "Expected '{' after condition");
const branches: UncertainBranches = {};
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
if (this.match(TokenType.HIGH)) {
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'high'");
const statements = this.blockContents();
branches.high = new BlockStatement(statements);
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after high branch");
} else if (this.match(TokenType.MEDIUM)) {
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'medium'");
const statements = this.blockContents();
branches.medium = new BlockStatement(statements);
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after medium branch");
} else if (this.match(TokenType.LOW)) {
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'low'");
const statements = this.blockContents();
branches.low = new BlockStatement(statements);
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after low branch");
} else if (this.match(TokenType.DEFAULT)) {
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'default'");
const statements = this.blockContents();
branches.default = new BlockStatement(statements);
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after default branch");
} else {
throw new ParseError("Expected 'high', 'medium', 'low', or 'default' branch", this.peek(), this.sourceCode);
}
}
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after uncertain if branches");
return new UncertainIfStatement(condition!, threshold, branches);
}
private ifStatement(): IfStatement {
this.consume(TokenType.LEFT_PAREN, "Expected '(' after 'if'");
const condition = this.expression();
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after if condition");
// For if statements, always treat { as a block statement
let thenStatement: Statement;
if (this.check(TokenType.LEFT_BRACE)) {
this.advance(); // consume {
thenStatement = this.blockStatement();
} else {
const stmt = this.statement();
if (!stmt) throw new ParseError("Expected statement", this.peek(), this.sourceCode);
thenStatement = stmt;
}
let elseStatement: Statement | undefined = undefined;
if (this.match(TokenType.ELSE)) {
// For else, also handle blocks directly
if (this.check(TokenType.LEFT_BRACE)) {
this.advance(); // consume {
elseStatement = this.blockStatement();
} else {
elseStatement = this.statement() || undefined;
}
}
return new IfStatement(condition!, thenStatement!, elseStatement);
}
private contextStatement(): ContextStatement {
this.consume(TokenType.CONTEXT, "Expected 'context' after 'in'");
const contextName = this.consume(TokenType.IDENTIFIER, "Expected context name").value;
// For context statements, handle blocks directly
let body: Statement;
if (this.check(TokenType.LEFT_BRACE)) {
this.advance(); // consume {
body = this.blockStatement();
} else {
const stmt = this.statement();
if (!stmt) throw new ParseError("Expected statement", this.peek(), this.sourceCode);
body = stmt;
}
let shiftTo: string | undefined = undefined;
if (this.match(TokenType.SHIFTING)) {
this.consume(TokenType.TO, "Expected 'to' after 'shifting'");
shiftTo = this.consume(TokenType.IDENTIFIER, "Expected context name after 'to'").value;
// Parse the shifting target block
if (this.check(TokenType.LEFT_BRACE)) {
this.advance(); // consume {
this.blockStatement(); // consume the shifting target block
} else {
this.statement(); // consume the shifting target statement
}
}
return new ContextStatement(contextName, body!, shiftTo);
}
private blockStatement(): BlockStatement {
const statements = this.blockContents();
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after block");
return new BlockStatement(statements);
}
private blockContents(): Statement[] {
const statements: Statement[] = [];
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
const stmt = this.statement();
if (stmt) {
statements.push(stmt);
}
}
return statements;
}
private expressionStatement(): Statement {
// Look for destructuring pattern at the start
if (this.check(TokenType.LEFT_BRACKET) || this.check(TokenType.LEFT_BRACE)) {
// Use lookahead to check if this is likely a destructuring assignment
const isLikelyDestructuring = this.isLikelyDestructuringPattern();
// console.log('At start of expressionStatement, token:', this.peek(), 'isLikelyDestructuring:', isLikelyDestructuring);
if (isLikelyDestructuring) {
const pattern = this.tryParseDestructuringPattern();
// console.log('Parsed pattern:', pattern);
if (pattern) {
// Check for confidence threshold (Option 1: [a, b] ~> 0.8 = array)
let confidenceThreshold: Expression | undefined;
if (this.match(TokenType.CONFIDENCE_ARROW)) {
const threshold = this.expression();
if (threshold) {
confidenceThreshold = threshold;
}
}
if (this.match(TokenType.EQUAL)) {
const value = this.expression();
this.match(TokenType.SEMICOLON);
return new DestructuringAssignment(pattern, value!, confidenceThreshold);
} else {
throw new ParseError("Expected '=' after destructuring pattern", this.peek(), this.sourceCode);
}
}
}
}
const expr = this.expression();
// Check if this is an assignment or compound assignment
if (expr instanceof IdentifierExpression) {
if (this.match(TokenType.EQUAL)) {
const value = this.expression();
// Consume optional semicolon
this.match(TokenType.SEMICOLON);
return new AssignmentStatement(expr.name, value!);
} else if (this.match(TokenType.PLUS_EQUAL)) {
// x += y becomes x = x + y
const right = this.expression();
const value = new BinaryExpression('+', expr, right!);
this.match(TokenType.SEMICOLON);
return new AssignmentStatement(expr.name, value);
} else if (this.match(TokenType.MINUS_EQUAL)) {
// x -= y becomes x = x - y
const right = this.expression();
const value = new BinaryExpression('-', expr, right!);
this.match(TokenType.SEMICOLON);
return new AssignmentStatement(expr.name, value);
} else if (this.match(TokenType.STAR_EQUAL)) {
// x *= y becomes x = x * y
const right = this.expression();
const value = new BinaryExpression('*', expr, right!);
this.match(TokenType.SEMICOLON);
return new AssignmentStatement(expr.name, value);
} else if (this.match(TokenType.SLASH_EQUAL)) {
// x /= y becomes x = x / y
const right = this.expression();
const value = new BinaryExpression('/', expr, right!);
this.match(TokenType.SEMICOLON);
return new AssignmentStatement(expr.name, value);
} else if (this.match(TokenType.PERCENT_EQUAL)) {
// x %= y becomes x = x % y
const right = this.expression();
const value = new BinaryExpression('%', expr, right!);
this.match(TokenType.SEMICOLON);
return new AssignmentStatement(expr.name, value);
} else if (this.match(TokenType.CONFIDENCE_PLUS_EQUAL)) {
// x ~+= y becomes x = x ~+ y
const right = this.expression();
const value = new BinaryExpression('~+', expr, right!);
this.match(TokenType.SEMICOLON);
return new AssignmentStatement(expr.name, value);
} else if (this.match(TokenType.CONFIDENCE_MINUS_EQUAL)) {
// x ~-= y becomes x = x ~- y
const right = this.expression();
const value = new BinaryExpression('~-', expr, right!);
this.match(TokenType.SEMICOLON);
return new AssignmentStatement(expr.name, value);
} else if (this.match(TokenType.CONFIDENCE_STAR_EQUAL)) {
// x ~*= y becomes x = x ~* y
const right = this.expression();
const value = new BinaryExpression('~*', expr, right!);
this.match(TokenType.SEMICOLON);
return new AssignmentStatement(expr.name, value);
} else if (this.match(TokenType.CONFIDENCE_SLASH_EQUAL)) {
// x ~/= y becomes x = x ~/ y
const right = this.expression();
const value = new BinaryExpression('~/', expr, right!);
this.match(TokenType.SEMICOLON);
return new AssignmentStatement(expr.name, value);
}
}
// Consume optional semicolon
this.match(TokenType.SEMICOLON);
// Wrap expression in an ExpressionStatement
return new ExpressionStatement(expr!);
}
private forStatement(): Statement {
// for statement can be either:
// 1. C-style: for i = 0; i < 10; i++ { ... }
// 2. For-in: for item in array { ... }
// 3. For-in with index: for item, index in array { ... }
// Check if this is a for-in loop by looking ahead
const checkPoint = this.current;
// Try to parse as for-in first
if (this.check(TokenType.IDENTIFIER)) {
this.advance();
if (this.check(TokenType.COMMA)) {
// for item, index in array
this.advance(); // consume comma
if (this.check(TokenType.IDENTIFIER)) {
this.advance();
if (this.check(TokenType.IN)) {
// Reset and parse as for-in with index
this.current = checkPoint;
return this.forInStatement();
}
}
} else if (this.check(TokenType.IN)) {
// for item in array
this.current = checkPoint;
return this.forInStatement();
}
}
// Reset position and parse as C-style for loop
this.current = checkPoint;
// C-style for loop: for init; condition; update { body }
let init: Statement | null = null;
let condition: Expression | null = null;
let update: Expression | null = null;
// Parse init
if (!this.check(TokenType.SEMICOLON)) {
if (this.check(TokenType.IDENTIFIER) && this.peekNext().type === TokenType.EQUAL) {
// Variable assignment
const identifier = this.advance().value;
this.consume(TokenType.EQUAL, "Expected '=' in assignment");
const value = this.expression();
if (!value) {
throw new ParseError("Expected expression after '='", this.previous(), this.sourceCode);
}
init = new AssignmentStatement(identifier, value);
} else {
// Expression
const expr = this.expression();
if (expr) {
init = new ExpressionStatement(expr);
}
}
}
if (!this.match(TokenType.SEMICOLON)) {
throw new ParseError("Expected ';' after for loop initializer", this.peek(), this.sourceCode);
}
// Parse condition
if (!this.check(TokenType.SEMICOLON)) {
condition = this.expression();
}
if (!this.match(TokenType.SEMICOLON)) {
throw new ParseError("Expected ';' after for loop condition", this.peek(), this.sourceCode);
}
// Parse update
if (!this.check(TokenType.LEFT_BRACE)) {
// Check if this is an assignment
if (this.check(TokenType.IDENTIFIER)) {
const checkPoint = this.current;
const identifier = this.advance();
if (this.match(TokenType.EQUAL)) {
// It's an assignment expression
const value = this.expression();
if (!value) {
throw new ParseError("Expected expression after '='", this.previous(), this.sourceCode);
}
update = new AssignmentExpression(identifier.value, value);
} else {
// Not an assignment, reset and parse as regular expression
this.current = checkPoint;
update = this.expression();
}
} else {
update = this.expression();
}
}
// Parse body
const body = this.statement();
if (!body) {
throw new ParseError("Expected body for for loop", this.peek(), this.sourceCode);
}
return new ForLoop(init, condition, update, body);
}
private forInStatement(): Statement {
// Parse variable name
if (!this.check(TokenType.IDENTIFIER)) {
throw new ParseError("Expected identifier in for-in loop", this.peek(), this.sourceCode);
}
const variable = this.advance().value;
// Check for optional index variable
let index: string | null = null;
if (this.match(TokenType.COMMA)) {
if (!this.check(TokenType.IDENTIFIER)) {
throw new ParseError("Expected identifier after comma in for-in loop", this.peek(), this.sourceCode);
}
index = this.advance().value;
}
// Expect 'in'
if (!this.match(TokenType.IN)) {
throw new ParseError("Expected 'in' in for-in loop", this.peek(), this.sourceCode);
}
// Parse iterable expression
const iterable = this.expression();
if (!iterable) {
throw new ParseError("Expected expression after 'in'", this.peek(), this.sourceCode);
}
// Parse body
const body = this.statement();
if (!body) {
throw new ParseError("Expected body for for-in loop", this.peek(), this.sourceCode);
}
return new ForInLoop(variable, index, iterable, body);
}
private whileStatement(): Statement {
// Parse condition
const condition = this.expression();
if (!condition) {
throw new ParseError("Expected condition in while loop", this.peek(), this.sourceCode);
}
// Parse body
const body = this.statement();
if (!body) {
throw new ParseError("Expected body for while loop", this.peek(), this.sourceCode);
}
return new WhileLoop(condition, body);
}
private doWhileStatement(): Statement {
// Parse body
const body = this.statement();
if (!body) {
throw new ParseError("Expected body for do-while loop", this.peek(), this.sourceCode);
}
// Expect 'while'
if (!this.match(TokenType.WHILE)) {
throw new ParseError("Expected 'while' after do-while body", this.peek(), this.sourceCode);
}
// Parse condition
const condition = this.expression();
if (!condition) {
throw new ParseError("Expected condition in do-while loop", this.peek(), this.sourceCode);
}
return new DoWhileLoop(body, condition);
}
private breakStatement(): Statement {
return new BreakStatement();
}
private continueStatement(): Statement {
return new ContinueStatement();
}
private uncertainForStatement(): Statement {
this.consume(TokenType.FOR, "Expected 'for' after 'uncertain'");
// Parse init, condition, update like regular for loop
let init: Statement | null = null;
let condition: Expression | null = null;
let update: Expression | null = null;
// Parse init
if (!this.check(TokenType.SEMICOLON)) {
if (this.check(TokenType.IDENTIFIER) && this.peekNext().type === TokenType.EQUAL) {
const identifier = this.advance().value;
this.consume(TokenType.EQUAL, "Expected '=' in assignment");
const value = this.expression();
if (!value) {
throw new ParseError("Expected expression after '='", this.previous(), this.sourceCode);
}
init = new AssignmentStatement(identifier, value);
} else {
const expr = this.expression();
if (expr) {
init = new ExpressionStatement(expr);
}
}
}
if (!this.match(TokenType.SEMICOLON)) {
throw new ParseError("Expected ';' after for loop initializer", this.peek(), this.sourceCode);
}
// Parse condition
if (!this.check(TokenType.SEMICOLON)) {
condition = this.expression();
}
if (!this.match(TokenType.SEMICOLON)) {
throw new ParseError("Expected ';' after for loop condition", this.peek(), this.sourceCode);
}
// Parse update
if (!this.check(TokenType.LEFT_BRACE)) {
if (this.check(TokenType.IDENTIFIER)) {
const checkPoint = this.current;
const identifier = this.advance();
if (this.match(TokenType.EQUAL)) {
const value = this.expression();
if (!value) {
throw new ParseError("Expected expression after '='", this.previous(), this.sourceCode);
}
update = new AssignmentExpression(identifier.value, value);
} else {
this.current = checkPoint;
update = this.expression();
}
} else {
update = this.expression();
}
}
// Parse uncertain branches
this.consume(TokenType.LEFT_BRACE, "Expected '{' after uncertain for loop header");
const branches = this.parseUncertainBranches();
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after uncertain for branches");
return new UncertainForLoop(init, condition, update, branches);
}
private uncertainWhileStatement(): Statement {
this.consume(TokenType.WHILE, "Expected 'while' after 'uncertain'");
// Parse condition - must be a confident expression
const condition = this.expression();
if (!condition) {
throw new ParseError("Expected condition in uncertain while loop", this.peek(), this.sourceCode);
}
// Parse uncertain branches
this.consume(TokenType.LEFT_BRACE, "Expected '{' after uncertain while condition");
const branches = this.parseUncertainBranches();
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after uncertain while branches");
return new UncertainWhileLoop(condition, branches);
}
private parseUncertainBranches(): UncertainBranches {
const branches: UncertainBranches = {};
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
if (this.match(TokenType.HIGH)) {
if (branches.high) {
throw new ParseError("Duplicate 'high' branch in uncertain statement", this.previous(), this.sourceCode);
}
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'high'");
const statements = this.blockContents();
branches.high = new BlockStatement(statements);
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after high branch");
} else if (this.match(TokenType.MEDIUM)) {
if (branches.medium) {
throw new ParseError("Duplicate 'medium' branch in uncertain statement", this.previous(), this.sourceCode);
}
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'medium'");
const statements = this.blockContents();
branches.medium = new BlockStatement(statements);
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after medium branch");
} else if (this.match(TokenType.LOW)) {
if (branches.low) {
throw new ParseError("Duplicate 'low' branch in uncertain statement", this.previous(), this.sourceCode);
}
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'low'");
const statements = this.blockContents();
branches.low = new BlockStatement(statements);
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after low branch");
} else if (this.match(TokenType.DEFAULT)) {
if (branches.default) {
throw new ParseError("Duplicate 'default' branch in uncertain statement", this.previous(), this.sourceCode);
}
this.consume(TokenType.LEFT_BRACE, "Expected '{' after 'default'");
const statements = this.blockContents();
branches.default = new BlockStatement(statements);
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after default branch");
} else {
throw new ParseError("Expected 'high', 'medium', 'low', or 'default' branch in uncertain statement", this.peek(), this.sourceCode);
}
}
return branches;
}
private importStatement(): ImportStatement {
const specifiers: ImportSpecifier[] = [];
let defaultImport: string | undefined;
let namespaceImport: string | undefined;
let source: string;
// Check for namespace import: import * as name from "module"
if (this.match(TokenType.STAR)) {
this.consume(TokenType.AS, "Expected 'as' after '*'");
namespaceImport = this.consume(TokenType.IDENTIFIER, "Expected namespace name after 'as'").value;
}
// Check for named imports: import {name1, name2} from "module"
else if (this.check(TokenType.LEFT_BRACE)) {
this.advance(); // consume {
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
const imported = this.consume(TokenType.IDENTIFIER, "Expected import name").value;
let local = imported; // Default to same name
// Check for renaming: import {original as renamed}
if (this.match(TokenType.AS)) {
local = this.consume(TokenType.IDENTIFIER, "Expected local name after 'as'").value;
}
specifiers.push(new ImportSpecifier(imported, local !== imported ? local : undefined));
if (this.match(TokenType.COMMA)) {
// Allow trailing comma
if (this.check(TokenType.RIGHT_BRACE)) {
break;
}
} else if (!this.check(TokenType.RIGHT_BRACE)) {
throw new ParseError("Expected ',' or '}' after import specifier", this.peek(), this.sourceCode);
}
}
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after import specifiers");
}
// Check for default import: import defaultName from "module"
else if (this.check(TokenType.IDENTIFIER)) {
defaultImport = this.advance().value;
// Check for mixed import: import defaultName, {named} from "module"
if (this.match(TokenType.COMMA)) {
if (this.match(TokenType.LEFT_BRACE)) {
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
const imported = this.consume(TokenType.IDENTIFIER, "Expected import name").value;
let local = imported;
if (this.match(TokenType.AS)) {
local = this.consume(TokenType.IDENTIFIER, "Expected local name after 'as'").value;
}
specifiers.push(new ImportSpecifier(imported, local !== imported ? local : undefined));
if (this.match(TokenType.COMMA)) {
if (this.check(TokenType.RIGHT_BRACE)) {
break;
}
} else if (!this.check(TokenType.RIGHT_BRACE)) {
throw new ParseError("Expected ',' or '}' after import specifier", this.peek(), this.sourceCode);
}
}
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after import specifiers");
}
}
} else {
// No valid import pattern found - this is an error
throw new ParseError("Expected import specifier", this.peek(), this.sourceCode);
}
// Expect 'from' keyword
this.consume(TokenType.FROM, "Expected 'from' after import specifiers");
// Parse module source
source = this.consume(TokenType.STRING, "Expected module path after 'from'").value;
this.match(TokenType.SEMICOLON); // Optional semicolon
return new ImportStatement(specifiers, source, defaultImport, namespaceImport);
}
private exportStatement(): ExportStatement {
// Check for different export patterns
// export default expression
if (this.match(TokenType.DEFAULT)) {
const declaration = this.expressionStatement();
return new ExportStatement(undefined, undefined, declaration, true);
}
// export * from "module"
if (this.match(TokenType.STAR)) {
this.consume(TokenType.FROM, "Expected 'from' after 'export *'");
const source = this.consume(TokenType.STRING, "Expected module path after 'from'").value;
this.match(TokenType.SEMICOLON); // Optional semicolon
return new ExportStatement(undefined, source, undefined, false, true);
}
// export {name1, name2} or export {name1, name2} from "module"
if (this.match(TokenType.LEFT_BRACE)) {
const specifiers: ExportSpecifier[] = [];
while (!this.check(TokenType.RIGHT_BRACE) && !this.isAtEnd()) {
const local = this.consume(TokenType.IDENTIFIER, "Expected export name").value;
let exported = local; // Default to same name
// Check for renaming: export {original as renamed}
if (this.match(TokenType.AS)) {
exported = this.consume(TokenType.IDENTIFIER, "Expected exported name after 'as'").value;
}
specifiers.push(new ExportSpecifier(local, exported !== local ? exported : undefined));
if (this.match(TokenType.COMMA)) {
// Allow trailing comma
if (this.check(TokenType.RIGHT_BRACE)) {
break;
}
} else if (!this.check(TokenType.RIGHT_BRACE)) {
throw new ParseError("Expected ',' or '}' after export specifier", this.peek(), this.sourceCode);
}
}
this.consume(TokenType.RIGHT_BRACE, "Expected '}' after export specifiers");
// Check for re-export: export {names} from "module"
let source: string | undefined;
if (this.match(TokenType.FROM)) {
source = this.consume(TokenType.STRING, "Expected module path after 'from'").value;
}
this.match(TokenType.SEMICOLON); // Optional semicolon
return new ExportStatement(specifiers, source);
}
// export statement (direct export)
const declaration = this.statement();
if (!declaration) {
throw new ParseError("Expected declaration after 'export'", this.peek(), this.sourceCode);
}
return new ExportStatement(undefined, undefined, declaration);
}
private functionDeclaration(isAsync: boolean = false): FunctionDeclaration {
// Parse function name
const name = this.consume(TokenType.IDENTIFIER, "Expected function name").value;
// Parse parameters
this.consume(TokenType.LEFT_PAREN, "Expected '(' after function name");
const parameters: LambdaParameter[] = [];
let restParameter: string | ArrayPattern | ObjectPattern | undefined = undefined;
if (!this.check(TokenType.RIGHT_PAREN)) {
do {
if (this.match(TokenType.SPREAD)) {
// Rest parameter
if (this.check(TokenType.IDENTIFIER)) {
restParameter = this.advance().value;
} else if (this.check(TokenType.LEFT_BRACKET) || this.check(TokenType.LEFT_BRACE)) {
// Rest with destructuring pattern
const pattern = this.tryParseDestructuringPattern();
if (!pattern) {
throw new ParseError("Expected parameter pattern after '...'", this.peek(), this.sourceCode);
}
restParameter = pattern;
} else {
throw new ParseError("Expected parameter name or pattern after '...'", this.peek(), this.sourceCode);
}
// Rest parameter must be last
if (this.match(TokenType.COMMA)) {
throw new ParseError("Rest parameter must be last formal parameter", this.previous(), this.sourceCode);
}
} else if (this.match(TokenType.IDENTIFIER)) {
if (restParameter) {
throw new ParseError("Rest parameter must be last formal parameter", this.previous(), this.sourceCode);
}
parameters.push(this.previous().value);
} else if (this.check(TokenType.LEFT_BRACKET) || this.check(TokenType.LEFT_BRACE)) {
// Destructuring pattern parameter
if (restParameter) {
throw new ParseError("Rest parameter must be last formal parameter", this.previous(), this.sourceCode);
}
const pattern = this.tryParseDestructuringPattern();
if (!pattern) {
throw new ParseError("Expected parameter pattern", this.peek(), this.sourceCode);
}
parameters.push(pattern);
} else {
throw new ParseError("Expected parameter name or pattern", this.peek(), this.sourceCode);
}
} while (this.match(TokenType.COMMA));
}
this.consume(TokenType.RIGHT_PAREN, "Expected ')' after parameters");
// Parse optional confidence annotation
let confidenceAnnotation: Expression | undefined;
if (this.match(TokenType.CONFIDENCE_ARROW)) {
const confExpr = this.expression();
if (!confExpr) {
throw new ParseError("Expected confidence expression after '~>'", this.previous(), this.sourceCode);
}
confidenceAnnotation = confExpr;
}
// Parse function body (must be a block)
this.consume(TokenType.LEFT_BRACE, "Expected '{' before function body");
const body = this.blockStatement();
return new FunctionDeclaration(name, parameters, body, restParameter, confidenceAnnotation, isAsync);
}
private returnStatement(): ReturnStatement {
let value: Expression | undefined;
// Check if there's a return value (not followed by semicolon or EOF)
if (!this.check(TokenType.SEMICOLON) && !this.isAtEnd() &&
!this.check(TokenType.RIGHT_BRACE)) {
value = this.expression() || undefined;
}
this.match(TokenType.SEMICOLON); // Optional semicolon
return new ReturnStatement(value);
}
private variableDeclaration(): VariableDeclaration {
// Get the declaration kind (const or let)
const kind = this.previous().value as 'const' | 'let';
// Check for destructuring pattern
if (this.check(TokenType.LEFT_BRACKET) || this.check(TokenType.LEFT_BRACE)) {
const pattern = this.tryParseDestructuringPattern();
if (pattern) {
// Pattern destructuring: const [a, b] = array or const {x, y} = obj
this.consume(TokenType.EQUAL, "Expected '=' after destructuring pattern");
const initializer = this.expression();
if (!initializer) {
throw new ParseError("Expected expression after '='", this.peek(), this.sourceCode);
}
this.match(TokenType.SEMICOLON); // Optional semicolon
return new VariableDeclaration(kind, '', initializer, pattern);
}
}
// Regular variable declaration: const/let name = value
const identifier = this.consume(TokenType.IDENTIFIER, "Expected variable name").value;
// const requires an initializer, let allows optional initializer
let initializer: Expression | undefined;
if (this.match(TokenType.EQUAL)) {
const expr = this.expression();
if (!expr) {
throw new ParseError("Expected expression after '='", this.previous(), this.sourceCode);
}
initializer = expr;
} else if (kind === 'const') {
throw new ParseError("const declarations must have an initializer", this.peek(), this.sourceCode);
}
this.match(TokenType.SEMICOLON); // Optional semicolon
return new VariableDeclaration(kind, identifier, initializer);
}
private expression(): Expression | null {
return this.pipeline();
}
private pipeline(): Expression | null {
let expr = this.ternary();
while (this.match(TokenType.PIPELINE, TokenType.CONFIDENCE_PIPELINE, TokenType.CONFIDENCE_THRESHOLD_GATE)) {
const operator = this.previous();
if (operator.type === TokenType.CONFIDENCE_THRESHOLD_GATE) {
// Handle threshold gate operator ~?>
const threshold = this.ternary();
if (!threshold) {
throw new ParseError("Expected threshold expression after '~?>'", this.previous(), this.sourceCode);
}
// Create threshold gate expression
expr = new BinaryExpression('~?>' as BinaryOperator, expr!, threshold);
} else {
// Handle regular and confidence pipeline operators
const isConfidencePipeline = operator.type === TokenType.CONFIDENCE_PIPELINE;
const right = this.ternary();
if (!right) {
throw new ParseError("Expected expression after pipeline operator", this.previous(), this.sourceCode);
}
// Replace placeholders in the right expression with the left expression
const pipelineExpr = this.replacePlaceholders(right, expr!);
// For confidence pipeline, wrap in a binary expression to preserve confidence
if (isConfidencePipeline) {
expr = new BinaryExpression('~|>' as BinaryOperator, expr!, pipelineExpr);
} else {
expr = pipelineExpr;
}
}
}
return expr;
}
private ternary(): Expression | null {
const expr = this.confidenceExpression();
if (this.match(TokenType.CONFIDENCE_QUESTION)) {
const trueBranch = this.expression();
if (!trueBranch) {
throw new ParseError("Expected expression after '~?'", this.previous(), this.sourceCode);
}
if (!this.match(TokenType.COLON)) {
throw new ParseError("Expected ':' after true branch of confident ternary operator", this.peek(), this.sourceCode);
}
const falseBranch = this.expression();
if (!falseBranch) {
throw new ParseError("Expected expression after ':'", this.previous(), this.sourceCode);
}
return new ConfidentTernaryExpression(expr!, trueBranch, falseBranch);
}
if (this.match(TokenType.QUESTION)) {
const trueBranch = this.expression();
if (!trueBranch) {
throw new ParseError("Expected expression after '?'", this.previous(), this.sourceCode);
}
if (!this.match(TokenType.COLON)) {
throw new ParseError("Expected ':' after true branch of ternary operator", this.peek(), this.sourceCode);
}
const falseBranch = this.expression();
if (!falseBranch) {
throw new ParseError("Expected expression after ':'", this.previous(), this.sourceCode);
}
return new TernaryExpression(expr!, trueBranch, falseBranch);
}
return expr;
}
private confidenceExpression(): Expression | null {
let expr = this.logicalOr();
if (this.match(TokenType.CONFIDENCE_ARROW)) {
const confidence = this.primary();
if (confidence) {
return new ConfidenceExpression(expr!, confidence);
}
throw new ParseError("Expected expression after '~>'", this.previous(), this.sourceCode);
}
// Handle confidence chaining operator (~~)
while (this.match(TokenType.CONFIDENCE_CHAIN)) {
const operator = this.previous().value as BinaryOperator;
const right = this.logicalOr();
expr = new BinaryExpression(operator, expr!, right!);
}
return expr;
}
private logicalOr(): Expression | null {
let expr = this.nullishCoalesce();
while (this.match(TokenType.OR, TokenType.CONFIDENCE_OR, TokenType.PARALLEL_CONFIDENCE, TokenType.THRESHOLD_GATE)) {
const operator = this.previous().value as BinaryOperator;
const right = this.nullishCoalesce();
expr = new BinaryExpression(operator, expr!, right!);
}
return expr;
}
private nullishCoalesce(): Expression | null {
let expr = this.confidenceCoalesce();
while (this.match(TokenType.QUESTION_QUESTION)) {
const operator = this.previous().value as BinaryOperator;
const right = this.confidenceCoalesce();
expr = new BinaryExpression(operator, expr!, right!);
}
return expr;
}
private confidenceCoalesce(): Expression | null {
let expr = this.logicalAnd();
while (this.match(TokenType.CONFIDENCE_COALESCE)) {
const operator = this.previous().value as BinaryOperator;
const right = this.logicalAnd();
expr = new BinaryExpression(operator, expr!, right!);
}
return expr;
}
private logicalAnd(): Expression | null {
let expr = this.equality();
while (this.match(TokenType.AND, TokenType.CONFIDENCE_AND)) {
const operatorToken = this.previous();
const operator = operatorToken.value as BinaryOperator;
const right = this.equality();
expr = new BinaryExpression(operator, expr!, right!).setLocation(operatorToken.line, operatorToken.column);
}
return expr;
}
private equality(): Expression | null {
let expr = this.comparison();
while (this.match(TokenType.NOT_EQUAL, TokenType.EQUAL_EQUAL, TokenType.NOT_EQUAL_EQUAL, TokenType.EQUAL_EQUAL_EQUAL, TokenType.CONFIDENCE_EQUAL, TokenType.CONFIDENCE_NOT_EQUAL)) {
const operator = this.previous().value as BinaryOperator;
const right = this.comparison();
expr = new BinaryExpression(operator, expr!, right!);
}
return expr;
}
private comparison(): Expression | null {
let expr = this.term();
while (this.match(TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL,
TokenType.CONFIDENCE_GREATER_EQUAL, TokenType.CONFIDENCE_LESS, TokenType.CONFIDENCE_LESS_EQUAL,
TokenType.INSTANCEOF)) {
const operator = this.previous().value as BinaryOperator;
const right = this.term();
expr = new BinaryExpression(operator, expr!, right!);
}
return expr;
}
private term(): Expression | null {
let expr = this.factor();
while (this.match(TokenType.MINUS, TokenType.PLUS, TokenType.CONFIDENCE_MINUS, TokenType.CONFIDENCE_PLUS)) {
const operator = this.previous().value as BinaryOperator;
const right = this.factor();
expr = new BinaryExpression(operator, expr!, right!);
}
return expr;
}
private factor(): Expression | null {
let expr = this.exponent();
while (this.match(TokenType.SLASH, TokenType.STAR, TokenType.PERCENT, TokenType.CONFIDENCE_SLASH, TokenType.CONFIDENCE_STAR)) {
const operator = this.previous().value as BinaryOperator;
const right = this.exponent();
expr = new BinaryExpression(operator, expr!, right!);
}
return expr;
}
private exponent(): Expression | null {
let expr = this.unary();
// Right-associative: parse from right to left
if (this.match(TokenType.STAR_STAR)) {
const operator = this.previous().value as BinaryOperator;
const right = this.exponent(); // Recursive for right-associativity
expr = new BinaryExpression(operator, expr!, right!);
}
return expr;
}
private unary(): Expression | null {
if (this.match(TokenType.NOT, TokenType.MINUS, TokenType.TILDE, TokenType.CONFIDENCE_EXTRACT, TokenType.TYPEOF)) {
const operator = this.previous().value as UnaryOperator;
const right = this.unary();
return new UnaryExpression(operator, right!);
}
if (this.match(TokenType.AWAIT)) {
const expr = this.unary();
if (!expr) {
throw new ParseError("Expected expression after 'await'", this.previous(), this.sourceCode);
}
return new AwaitExpression(expr);
}
return this.call();
}
private call(): Expression | null {
const expr = this.primary();
let current = expr;
while (current) {
if (this.match(TokenType.LEFT_PAREN)) {
current = this.finishCall(current);