PSS 3.0 Architecture & Design¶
This document describes the implementation architecture and design decisions for PSS 3.0 support in pssparser.
Overview¶
PSS 3.0 support was implemented across 7 phases over the course of the PSS 3.0 upgrade plan, with each phase building on the previous:
Foundation & Assessment
Grammar Completion
AST Implementation
Visitor Implementation
Semantic Validation (partial)
Linking & Resolution
Testing & Validation
This document focuses on architectural decisions and design rationale.
Architecture Layers¶
The parser follows a layered architecture:
┌─────────────────────────────────────┐
│ Application Layer │
│ (Python/C++ API consumers) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Semantic Layer │
│ (Linking, Type Checking) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ AST Layer │
│ (Abstract Syntax Tree) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Visitor Layer │
│ (ANTLR Visitors) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Parser Layer │
│ (ANTLR Generated Parser) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Lexer Layer │
│ (ANTLR Generated Lexer) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Input (PSS Source Code) │
└─────────────────────────────────────┘
Grammar Design¶
Dual Grammar Approach¶
The parser uses two separate grammars for optimal performance:
PSSParser.g4 - Procedural code context
Action exec blocks
Function bodies
Procedural statements
Import declarations
PSSExprParser.g4 - Constraint context
Constraint expressions
Constraint blocks
Constraint-specific operators
This separation provides:
Better error messages (context-aware)
Reduced ambiguity
Faster parsing (smaller rule sets)
Cleaner grammar organization
Grammar Optimization Strategies¶
The grammar deviates from literal BNF translation for performance:
1. Left-Recursion Elimination
PSS Spec BNF uses left-recursion:
// Spec (theoretical):
expression: expression '+' term | term ;
ANTLR grammar uses right-recursion with precedence:
// Implementation:
expression: term ('+' term)* ;
2. Precedence Climbing
Instead of separate rules for each precedence level, uses ANTLR’s built-in precedence mechanism for expressions.
3. Rule Consolidation
Multiple similar BNF rules consolidated into parameterized rules:
// Instead of:
// array_type: 'array' '<' type ',' size '>' ;
// list_type: 'list' '<' type '>' ;
// set_type: 'set' '<' type '>' ;
// Consolidated:
collection_type:
('array' | 'list' | 'set' | 'map')
'<' type_parameter_list '>' ;
Token Design¶
PSS 3.0 adds 8 new keywords:
monitor- Monitor declarationsyield- Control flowrandomize- Procedural randomizationatomic- Atomic blockseventually- Temporal operatorconcat- Temporal concatenationoverlap- Temporal overlapschedule- Temporal scheduling
All keywords are reserved tokens to prevent identifier conflicts.
AST Design¶
Node Hierarchy¶
The AST follows an object-oriented hierarchy:
IBase (root)
├── IScope
│ ├── IGlobalScope
│ ├── ITypeScope
│ │ ├── IComponent
│ │ ├── IAction
│ │ ├── IMonitor ← NEW PSS 3.0
│ │ └── ...
│ └── ...
├── IStmt
│ ├── IActivityStmt
│ │ ├── IActivityAtomicBlock ← NEW PSS 3.0
│ │ └── ...
│ ├── IProceduralStmt
│ │ ├── IProceduralStmtRandomize ← NEW PSS 3.0
│ │ └── ...
│ └── IMonitorActivityStmt ← NEW PSS 3.0
│ ├── IMonitorActivitySequence
│ ├── IMonitorActivityConcat
│ ├── IMonitorActivityEventually
│ └── ...
└── IExpr
├── IExprSubstring ← NEW PSS 3.0 (planned)
└── ...
Design Principles¶
1. Immutability Where Possible
AST nodes are largely immutable after construction, reducing bugs.
2. Smart Pointers
Use of unique pointers (IMonitorUP) for ownership, raw pointers for references.
3. Visitor Pattern
AST supports visitor pattern for traversal and transformation.
4. Type Safety
Strong typing prevents invalid AST construction.
Monitor AST Design¶
Monitors follow the TypeScope pattern like Actions and Components:
class IMonitor : public ITypeScope {
public:
// Properties
virtual bool getIsAbstract() const = 0;
virtual void setIsAbstract(bool) = 0;
// Children
virtual std::vector<IMonitorUP> &getMonitors() = 0;
virtual std::vector<IMonitorActivityDeclUP> &getActivities() = 0;
virtual std::vector<IConstraintUP> &getConstraints() = 0;
// Inheritance
virtual ITypeRef *getSuperType() const = 0;
virtual void setSuperType(ITypeRef*) = 0;
};
Design Rationale:
Monitors are TypeScopes - can contain nested types
Abstract flag via getter/setter (not constructor) for visitor flexibility
Activities stored as declarations (parallel to action activities)
Constraints separate from activities (clearer semantics)
Monitor Activity AST¶
Activities use a statement hierarchy similar to procedural statements:
class IMonitorActivityStmt : public IBase {
// Base class for all monitor activities
};
class IMonitorActivitySequence : public IMonitorActivityStmt {
// Sequential composition
virtual std::vector<IMonitorActivityStmtUP> &getActivities() = 0;
};
class IMonitorActivityConcat : public IMonitorActivityStmt {
// Temporal concatenation (##)
virtual std::vector<IMonitorActivityStmtUP> &getActivities() = 0;
};
Design Rationale:
Separate hierarchy from action activities (different semantics)
Temporal operators as first-class nodes
Composable - activities nest naturally
String Enhancement Design¶
String methods use an enumeration for efficient dispatch:
enum class StringMethodId {
NoMethod = 0,
Size,
Find,
FindLast,
FindAll,
Lower,
Upper,
Split,
Chars
};
class IExprMemberPathElem {
// ...
virtual StringMethodId getStringMethodId() const = 0;
virtual void setStringMethodId(StringMethodId) = 0;
};
Design Rationale:
Reuse existing member access expression infrastructure
Enumeration for efficient type checking
Extensible for future string methods
Substring Representation:
Currently uses subscript mechanism:
// s[0..4] represented as:
IExprMemberPathElem {
subscript[0] = expr(0) // start
subscript[1] = expr(4) // end
}
Future Enhancement: May add dedicated IExprSubstring node for better
semantic analysis.
Visitor Implementation¶
Visitor Pattern¶
ANTLR generates a base visitor class. Implementation overrides methods for relevant grammar rules:
class AstBuilderInt : public PSSParserBaseVisitor {
public:
// Override for each grammar rule
virtual antlrcpp::Any visitMonitor_declaration(
PSSParser::Monitor_declarationContext *ctx) override;
// Returns AST node wrapped in antlrcpp::Any
};
Visitor Design Patterns¶
1. Factory Pattern
AST nodes created via factory:
ast::IFactory *factory = ast::getFactory();
ast::IMonitorUP monitor = factory->mkMonitor(name, is_abstract);
2. Builder Pattern
Complex nodes built incrementally:
antlrcpp::Any AstBuilderInt::visitMonitor_declaration(
PSSParser::Monitor_declarationContext *ctx) {
// Create monitor
ast::IMonitorUP monitor = m_factory->mkMonitor(
ctx->identifier()->getText(),
false // not abstract
);
// Add template parameters
if (ctx->template_param_decl_list()) {
// ... process templates
}
// Add super type
if (ctx->super_spec()) {
// ... process inheritance
}
// Process body
for (auto *item : ctx->monitor_body_item()) {
// ... add body items
}
return monitor.release();
}
3. Context Stack
Maintains stack of scopes during traversal:
class AstBuilderInt {
std::vector<ast::IScope*> m_scope_stack;
ast::IScope *currentScope() {
return m_scope_stack.back();
}
void pushScope(ast::IScope *scope) {
m_scope_stack.push_back(scope);
}
void popScope() {
m_scope_stack.pop_back();
}
};
Error Handling in Visitors¶
Visitors report errors via error listener:
if (!isValidContext(ctx)) {
m_error_listener->reportError(
ctx->getStart(),
"Invalid context for monitor declaration"
);
return nullptr;
}
Semantic Validation¶
Validation Phases¶
Semantic validation occurs in multiple phases:
1. AST Construction (Visitors)
Basic syntax validation
Structural correctness
2. Linking Phase
Name resolution
Type resolution
Reference resolution
3. Type Checking Phase
Type compatibility
Constraint validation
Method signature validation
4. Context Validation Phase (Partial Implementation)
Platform qualifier validation
Yield context validation
Monitor field restrictions
Currently Implemented¶
✅ Name Resolution
Symbol table construction
Cross-file references
Template instantiation
✅ Type Resolution
Type references resolved
Template parameter substitution
Collection type validation
✅ Basic Type Checking
Expression type compatibility
Assignment type checking
Function parameter validation
Partially Implemented¶
⚠️ Context Validation
Yield context not validated
Platform qualifier calls not checked
Atomic block context not validated
⚠️ Monitor Validation
Field restrictions not enforced
Cover statement scope not checked
Abstract monitor instantiation not prevented
⚠️ String Method Validation
Return types not validated
Parameter types not fully checked
Context restrictions not enforced
Design for Future Implementation¶
Context validation will use a context stack:
class SemanticValidator {
enum class ExecContext {
None,
Target,
Solve,
Body
};
std::stack<ExecContext> m_context_stack;
void validateYield(IProceduralStmtYield *stmt) {
if (m_context_stack.top() != ExecContext::Target) {
reportError(stmt, "yield only allowed in target exec");
}
}
};
Linking and Resolution¶
Symbol Table Structure¶
Two-level symbol table:
1. Physical View (per-file)
IGlobalScopeper fileContains declarations as written
2. Logical View (global)
RootSymbolTableacross all filesMerges declarations by namespace
Handles
extendstatements
Monitor Resolution¶
Monitors resolve like other type scopes:
Register monitor in symbol table
Resolve inheritance (super type)
Resolve action/monitor references in activities
Resolve cover statement monitor references
Reference Type Linking¶
Reference types link in two phases:
1. Type Resolution
Resolve the referenced type:
array<ref Action, 10> actions;
// ^^^^^^ - resolve to Action type
2. Reference Validation
Validate reference usage contexts (future work).
Extension Mechanism¶
PSS supports extending existing declarations:
extend monitor MyMonitor {
// Additional body items
}
Extension Resolution¶
Parse extension as separate AST node
During linking, find original declaration
Merge extension items into original
Validate compatibility
Currently, basic extension works for monitors similar to components/actions.
Performance Considerations¶
Parse Performance¶
Metrics:
Average parse time: ~1-2ms per 100 lines
Memory usage: ~500 bytes per AST node
No significant PSS 3.0 overhead (< 5%)
Optimizations:
Efficient grammar (minimizes backtracking)
Smart pointer reuse
Lazy evaluation where possible
Memory Management¶
Strategy:
Unique pointers for ownership (
IMonitorUP)Raw pointers for references (
IMonitor*)ANTLR contexts cleaned up automatically
Memory Profile:
Small models: < 10MB
Large models: ~50-100MB
No memory leaks detected (Valgrind clean)
Threading Model¶
Parser is not thread-safe:
Create separate parser instances per thread
AST nodes are not thread-safe
No global state (safe for multi-process)
Extensibility¶
Adding New PSS Features¶
To add a new PSS feature:
Update Grammar
Add tokens to lexer
Add productions to parser
Test grammar with ANTLR
Define AST
Create node classes in YAML
Run code generator
Compile generated code
Implement Visitor
Add visitor method
Create and populate AST node
Handle context appropriately
Add Tests
Unit tests for parsing
Integration tests for linking
Validation tests for semantics
Document
User documentation
API documentation
Examples
Plugin Architecture (Future)¶
Future versions may support plugins for:
Custom AST transformations
Additional semantic checks
Code generation backends
Analysis tools
Design Decisions¶
Key Decisions and Rationale¶
1. ANTLR4 vs Hand-Written Parser
Decision: Use ANTLR4
Rationale: - Faster development - Easier grammar maintenance - Good error recovery - Industry standard
2. Dual Grammar Approach
Decision: Separate procedural and expression grammars
Rationale: - Clearer separation of contexts - Better error messages - Reduced ambiguity - PSS has distinct contexts
3. YAML for AST Definitions
Decision: Define AST in YAML, generate C++/Python
Rationale: - Single source of truth - Automatic language binding generation - Consistent API across languages - Easy to maintain
4. Smart Pointers for Ownership
Decision: Use unique pointers for ownership
Rationale: - Clear ownership semantics - Automatic memory management - Prevents leaks - Modern C++ best practice
5. Substring Reuses Subscript
Decision: Don’t create dedicated ExprSubstring initially
Rationale: - Faster implementation - Functionally equivalent for parsing - Can enhance later if needed - Minimal user impact
6. Incremental Semantic Validation
Decision: Ship grammar/AST before full validation
Rationale: - Unblocks users faster - Grammar is most critical - Validation can be added incrementally - Clear documentation of limitations
Trade-offs¶
Grammar Complexity vs Performance
More complex grammar = harder to maintain
But better performance and error messages
Decision: Optimize for performance
AST Completeness vs Simplicity
More AST nodes = more complete representation
But more code to maintain
Decision: Optimize for completeness
Validation Strictness vs Usability
Strict validation = catches more errors
But may block valid use cases
Decision: Incremental validation
Testing Strategy¶
Test Pyramid¶
┌────────────┐
│ E2E Tests │ (Few)
└────────────┘
┌───────────────────┐
│ Integration Tests │ (Some)
└───────────────────┘
┌─────────────────────────────┐
│ Unit Tests │ (Many)
└─────────────────────────────┘
Unit Test Strategy¶
Each feature has dedicated unit tests:
Parsing tests (grammar validation)
AST construction tests (visitor validation)
Linking tests (resolution validation)
Type checking tests (semantic validation)
Integration Test Strategy¶
Combined feature tests:
Multiple features in single file
Cross-file references
Complex scenarios
Real-world examples
Test Organization¶
tests/
├── src/
│ ├── TestPSS30Grammar.cpp # PSS 3.0 specific
│ ├── TestMonitors.cpp # Monitor feature tests
│ ├── TestStringMethods.cpp # String feature tests
│ └── ...
└── pss_examples/
├── monitor_examples/ # Spec examples
├── string_examples/
└── ...
Code Quality¶
Style Guidelines¶
C++ Style:
Google C++ Style Guide (mostly)
4-space indentation
Member variables prefixed with
m_Const correctness enforced
Python Style:
PEP 8 compliant
Type hints where applicable
Docstrings for public APIs
Code Review Process¶
All changes reviewed for:
Correctness
Performance
Maintainability
Test coverage
Documentation
Future Enhancements¶
Planned Improvements¶
Complete Semantic Validation
All context validations
Complete type checking
Full constraint validation
Enhanced AST
Dedicated ExprSubstring node
Better source location tracking
AST cloning for reference types
Performance Optimization
Incremental parsing
AST caching
Lazy linking
Tool Integration
Language Server Protocol
IDE integration
Static analysis tools
Additional Language Features
Future PSS versions
Vendor extensions
Custom pragmas
See Also¶
PSS 3.0 Features - Feature documentation
PSS 3.0 API Documentation - API reference
AST Structure - AST structure details
../PSS_3.0_UPGRADE_PLAN - Implementation plan