Streaming Validation
The streaming validator enables real-time validation of Prism code as it's being written or generated, making it ideal for integration with LLMs and code editors.
Overview
import { StreamingValidator } from '@prism-lang/validator';
const validator = new StreamingValidator();
// Reset state for new session
validator.resetStreaming();
// Validate code chunks as they arrive
const result1 = validator.validateStreaming("const x = ");
const result2 = validator.validateStreaming("llm(");
const result3 = validator.validateStreaming("\"What is");
Core Features
1. Incremental Parsing
The validator maintains parsing state across chunks:
validator.resetStreaming();
// Chunk 1: Incomplete but valid so far
let result = validator.validateStreaming("const result = ");
console.log(result.isValid); // true
console.log(result.isComplete); // false
// Chunk 2: Complete statement
result = validator.validateStreaming("42");
console.log(result.isValid); // true
console.log(result.isComplete); // true
2. Intelligent Completions
Get context-aware completions for partial code:
validator.resetStreaming();
validator.validateStreaming("uncertain if (x) {");
validator.validateStreaming(" high { }");
const completions = validator.getStreamingCompletions();
// Returns: ["medium {", "low {", "default {"]
3. Error Recovery
The validator can recover from errors and continue:
// Even with an error, it continues tracking
validator.validateStreaming("const x = ");
validator.validateStreaming("@#!"); // Invalid
validator.validateStreaming(" // Fixed: const x = 42");
const state = validator.getStreamingState();
console.log(state.hasErrors); // true
console.log(state.canRecover); // true
LLM Integration
Code Generation Validation
async function generatePrismCode(prompt: string) {
const validator = new StreamingValidator();
validator.resetStreaming();
const stream = await llm.streamCompletion(prompt);
let fullCode = "";
for await (const chunk of stream) {
fullCode += chunk;
const validation = validator.validateStreaming(chunk);
if (!validation.isValid) {
// Provide feedback to LLM
const error = validation.errors[0];
const correction = await llm.complete(
`Fix this Prism syntax error: ${error.message}\nCode: ${fullCode}`
);
fullCode += correction;
validator.validateStreaming(correction);
}
}
return fullCode;
}
Guided Generation
// Use completions to guide LLM
const validator = new StreamingValidator();
validator.resetStreaming();
async function guidedGeneration() {
let code = "";
// Start with a partial statement
code += "uncertain if (";
validator.validateStreaming(code);
// Get valid completions
const completions = validator.getStreamingCompletions();
// e.g., ["condition)", "~result > 0.5)"]
// Use LLM to pick best completion
const chosen = await llm.selectBest(completions, context);
code += chosen;
validator.validateStreaming(chosen);
return code;
}
Editor Integration
Real-time Validation
class PrismEditor {
private validator = new StreamingValidator();
private lastValidCode = "";
onTextChange(newText: string) {
// Find the change
const diff = this.findDiff(this.lastValidCode, newText);
if (diff.isAppend) {
// Validate only the new chunk
const result = this.validator.validateStreaming(diff.added);
this.updateErrorMarkers(result.errors);
} else {
// Reset for non-append changes
this.validator.resetStreaming();
this.validator.validateStreaming(newText);
}
}
}
Autocomplete Provider
class PrismAutocomplete {
private validator = new StreamingValidator();
async provideCompletions(
document: TextDocument,
position: Position
) {
// Get code up to cursor
const textUntilPosition = document.getText(
new Range(0, 0, position.line, position.character)
);
this.validator.resetStreaming();
this.validator.validateStreaming(textUntilPosition);
const completions = this.validator.getStreamingCompletions();
return completions.map(text => ({
label: text,
kind: CompletionItemKind.Snippet,
insertText: text
}));
}
}
API Reference
StreamingValidator Methods
interface StreamingValidator {
// Reset validator state
resetStreaming(): void;
// Validate a chunk of code
validateStreaming(chunk: string): StreamingResult;
// Get possible completions
getStreamingCompletions(): string[];
// Get current state
getStreamingState(): StreamingState;
// Get accumulated code
getAccumulatedCode(): string;
}
StreamingResult
interface StreamingResult {
isValid: boolean;
isComplete: boolean;
errors: ValidationError[];
warnings: ValidationWarning[];
expectedTokens?: string[];
ast?: PartialAST;
}
StreamingState
interface StreamingState {
inString: boolean;
inComment: boolean;
openBrackets: number;
openBraces: number;
hasErrors: boolean;
canRecover: boolean;
currentContext: 'statement' | 'expression' | 'block';
}
Performance Considerations
- Chunk Size: Larger chunks are more efficient
- Reset Frequency: Reset only when necessary
- Memory Usage: Old chunks are compressed
- Completion Cache: Completions are cached
// Optimal chunk size: ~100-500 characters
const CHUNK_SIZE = 200;
// Process in batches
for (let i = 0; i < code.length; i += CHUNK_SIZE) {
const chunk = code.slice(i, i + CHUNK_SIZE);
validator.validateStreaming(chunk);
}
Best Practices
- Reset on New Context: Always reset when starting new code
- Handle Incompleteness: Check
isComplete
before final validation - Use Completions: Guide generation with valid completions
- Monitor State: Track parser state for better error recovery
// Example: Robust streaming validation
function validateStream(codeStream: AsyncIterable<string>) {
const validator = new StreamingValidator();
validator.resetStreaming();
let lastValid = "";
for await (const chunk of codeStream) {
const result = validator.validateStreaming(chunk);
if (result.isValid && result.isComplete) {
lastValid = validator.getAccumulatedCode();
}
if (result.errors.length > 0 && !result.canRecover) {
// Rollback to last valid state
validator.resetStreaming();
validator.validateStreaming(lastValid);
}
}
return validator.getAccumulatedCode();
}