sim_kernel/pratt/types.rs
1use std::collections::BTreeMap;
2
3use crate::{Error, Result, Symbol};
4
5/// A single operator entry in a [`PrattTable`]: its symbol, fixity, binding
6/// powers, and the expression form it produces.
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct PrattOperator {
9 /// Symbol that triggers this operator during parsing.
10 pub symbol: Symbol,
11 /// How the operator binds relative to its operands.
12 pub fixity: Fixity,
13 /// Left binding power, governing how tightly the operator binds to its left.
14 pub left_bp: u16,
15 /// Right binding power, governing how tightly the operator binds to its right.
16 pub right_bp: u16,
17 /// Expression form produced when this operator matches.
18 pub result: PrattResult,
19}
20
21/// The grammatical position an operator occupies relative to its operands.
22#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
23pub enum Fixity {
24 /// Operator precedes its single operand (for example, unary minus).
25 Prefix,
26 /// Left-associative infix operator between two operands.
27 InfixLeft,
28 /// Right-associative infix operator between two operands.
29 InfixRight,
30 /// Operator follows its single operand (for example, a factorial mark).
31 Postfix,
32 /// Operator with operands interleaved among multiple fixed parts.
33 Mixfix,
34}
35
36/// The expression form a matched [`PrattOperator`] produces.
37#[derive(Clone, Debug, PartialEq, Eq)]
38pub enum PrattResult {
39 /// Build a binary expression from the operator and its two operands.
40 ExprInfix,
41 /// Build a unary expression from the operator and its trailing operand.
42 ExprPrefix,
43 /// Build a unary expression from the operator and its leading operand.
44 ExprPostfix,
45 /// Emit a call to the named callable with the operands as arguments.
46 Call(Symbol),
47 /// Emit a custom form keyed by the given symbol for the grammar to interpret.
48 Custom(Symbol),
49}
50
51/// A registry of [`PrattOperator`] entries indexed by fixity for precedence
52/// parsing.
53#[derive(Clone, Debug, Default, PartialEq, Eq)]
54pub struct PrattTable {
55 prefix: BTreeMap<String, PrattOperator>,
56 infix: BTreeMap<String, PrattOperator>,
57 postfix: BTreeMap<String, PrattOperator>,
58}
59
60impl PrattTable {
61 /// Creates an empty table with no registered operators.
62 pub fn new() -> Self {
63 Self::default()
64 }
65
66 /// Registers an operator under its fixity, replacing any prior entry for
67 /// the same symbol; [`Fixity::Mixfix`] operators are not stored.
68 pub fn register(&mut self, operator: PrattOperator) {
69 let key = operator.symbol.to_string();
70 match operator.fixity {
71 Fixity::Prefix => {
72 self.prefix.insert(key, operator);
73 }
74 Fixity::InfixLeft | Fixity::InfixRight => {
75 self.infix.insert(key, operator);
76 }
77 Fixity::Postfix => {
78 self.postfix.insert(key, operator);
79 }
80 Fixity::Mixfix => {}
81 }
82 }
83
84 /// Looks up the null-denotation (prefix) operator for a leading token, if any.
85 pub fn lookup_nud(&self, token: &Token) -> Option<PrattOperator> {
86 self.prefix.get(token.symbol_text()?).cloned()
87 }
88
89 /// Looks up the left-denotation operator for a token, preferring a postfix
90 /// entry over an infix one, if any.
91 pub fn lookup_led(&self, token: &Token) -> Option<PrattOperator> {
92 self.postfix
93 .get(token.symbol_text()?)
94 .cloned()
95 .or_else(|| self.infix.get(token.symbol_text()?).cloned())
96 }
97
98 /// Returns the infix operator for the symbol, or an error if none is registered.
99 pub fn require_infix(&self, symbol: &Symbol) -> Result<&PrattOperator> {
100 self.infix
101 .get(&symbol.to_string())
102 .ok_or_else(|| Error::Eval(format!("missing infix operator {}", symbol)))
103 }
104
105 /// Returns the prefix operator for the symbol, or an error if none is registered.
106 pub fn require_prefix(&self, symbol: &Symbol) -> Result<&PrattOperator> {
107 self.prefix
108 .get(&symbol.to_string())
109 .ok_or_else(|| Error::Eval(format!("missing prefix operator {}", symbol)))
110 }
111
112 /// Returns the postfix operator for the symbol, or an error if none is registered.
113 pub fn require_postfix(&self, symbol: &Symbol) -> Result<&PrattOperator> {
114 self.postfix
115 .get(&symbol.to_string())
116 .ok_or_else(|| Error::Eval(format!("missing postfix operator {}", symbol)))
117 }
118
119 /// Collects every registered operator across all fixities.
120 pub fn operators(&self) -> Vec<PrattOperator> {
121 self.prefix
122 .values()
123 .chain(self.infix.values())
124 .chain(self.postfix.values())
125 .cloned()
126 .collect()
127 }
128}
129
130/// A lexical token consumed by the precedence parser.
131#[derive(Clone, Debug, PartialEq, Eq)]
132pub enum Token {
133 /// An identifier token carrying its text.
134 Ident(String),
135 /// A numeric literal token carrying its source text.
136 Number(String),
137 /// A string literal token carrying its contents.
138 String(String),
139 /// An opening parenthesis.
140 OpenParen,
141 /// A closing parenthesis.
142 CloseParen,
143 /// An argument separator comma.
144 Comma,
145 /// An operator token carrying its symbol text.
146 Operator(String),
147}
148
149impl Token {
150 /// Returns the symbol text for operator and identifier tokens, or `None`
151 /// for tokens that carry no operator-table key.
152 pub fn symbol_text(&self) -> Option<&str> {
153 match self {
154 Self::Operator(text) | Self::Ident(text) => Some(text.as_str()),
155 _ => None,
156 }
157 }
158}
159
160/// Parses a raw operator string into a [`Symbol`], splitting on the first `/`
161/// or `.` into a qualified namespace and name when present.
162pub fn parse_symbol(raw: &str) -> Symbol {
163 match raw.split_once('/').or_else(|| raw.split_once('.')) {
164 Some((namespace, name)) => Symbol::qualified(namespace.to_owned(), name.to_owned()),
165 None => Symbol::new(raw.to_owned()),
166 }
167}