Skip to main content

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}