use core::fmt;
pub mod compile;
pub mod compose;
pub mod deltas;
pub mod idl42;
pub mod toy;
pub mod validate;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum IdlVersion {
V3_5,
V4_0,
V4_1,
V4_2,
}
impl Default for IdlVersion {
fn default() -> Self {
Self::V4_2
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ProductionId(pub u32);
impl ProductionId {
#[inline]
#[must_use]
pub const fn as_usize(self) -> usize {
self.0 as usize
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TokenRuleId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SpecRef {
pub doc: &'static str,
pub section: &'static str,
}
impl fmt::Display for SpecRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} §{}", self.doc, self.section)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TokenKind {
Keyword(&'static str),
Punct(&'static str),
Ident,
IntegerLiteral,
FloatLiteral,
StringLiteral,
CharLiteral,
BoolLiteral,
WideCharLiteral,
WideStringLiteral,
FixedPtLiteral,
EndOfInput,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RepeatKind {
ZeroOrMore,
OneOrMore,
Optional,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Symbol {
Terminal(TokenKind),
Nonterminal(ProductionId),
Repeat(RepeatKind, &'static [Symbol]),
Choice(&'static [&'static [Symbol]]),
}
impl Symbol {
#[inline]
#[must_use]
pub const fn is_terminal(self) -> bool {
matches!(self, Self::Terminal(_))
}
#[inline]
#[must_use]
pub const fn is_nonterminal(self) -> bool {
matches!(self, Self::Nonterminal(_))
}
}
#[derive(Debug, Clone, Copy)]
pub struct Alternative {
pub name: Option<&'static str>,
pub symbols: &'static [Symbol],
pub note: Option<&'static str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AltRef {
pub index: usize,
pub name: Option<&'static str>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AstHint {
Named(&'static str),
}
#[derive(Debug, Clone, Copy)]
pub struct Production {
pub id: ProductionId,
pub name: &'static str,
pub spec_ref: SpecRef,
pub alternatives: &'static [Alternative],
pub ast_hint: Option<AstHint>,
}
#[derive(Debug, Clone, Copy)]
pub struct TokenRule {
pub id: TokenRuleId,
pub kind: TokenKind,
pub pattern: &'static str,
}
#[derive(Debug, Clone, Copy)]
pub struct Grammar {
pub name: &'static str,
pub version: IdlVersion,
pub productions: &'static [Production],
pub start: ProductionId,
pub token_rules: &'static [TokenRule],
}
pub trait GrammarLike {
fn production(&self, id: ProductionId) -> Option<&Production>;
fn start(&self) -> ProductionId;
fn productions_slice(&self) -> &[Production];
}
impl GrammarLike for Grammar {
fn production(&self, id: ProductionId) -> Option<&Production> {
self.productions.iter().find(|p| p.id == id)
}
fn start(&self) -> ProductionId {
self.start
}
fn productions_slice(&self) -> &[Production] {
self.productions
}
}
impl Grammar {
#[must_use]
pub fn production(&self, id: ProductionId) -> Option<&Production> {
self.productions.iter().find(|p| p.id == id)
}
#[must_use]
pub fn start_production(&self) -> Option<&Production> {
self.production(self.start)
}
#[inline]
#[must_use]
pub fn production_count(&self) -> usize {
self.productions.len()
}
pub fn productions_iter(&self) -> impl Iterator<Item = &Production> {
self.productions.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
const PROD_DUMMY_MODULE: Production = Production {
id: ProductionId(0),
name: "dummy_module",
spec_ref: SpecRef {
doc: "TEST",
section: "0.0",
},
alternatives: &[Alternative {
name: None,
symbols: &[
Symbol::Terminal(TokenKind::Keyword("module")),
Symbol::Terminal(TokenKind::Ident),
],
note: None,
}],
ast_hint: None,
};
const DUMMY_GRAMMAR: Grammar = Grammar {
name: "dummy",
version: IdlVersion::V4_2,
productions: &[PROD_DUMMY_MODULE],
start: ProductionId(0),
token_rules: &[],
};
#[test]
fn default_idl_version_is_v4_2() {
assert_eq!(IdlVersion::default(), IdlVersion::V4_2);
}
#[test]
fn production_id_converts_to_usize() {
assert_eq!(ProductionId(42).as_usize(), 42);
}
#[test]
fn spec_ref_displays_with_paragraph_sign() {
let sref = SpecRef {
doc: "OMG IDL 4.2",
section: "7.4.1.4.4.2",
};
assert_eq!(format!("{sref}"), "OMG IDL 4.2 §7.4.1.4.4.2");
}
#[test]
fn symbol_classifies_terminals_and_nonterminals() {
let term = Symbol::Terminal(TokenKind::Ident);
let nonterm = Symbol::Nonterminal(ProductionId(0));
let rep = Symbol::Repeat(RepeatKind::ZeroOrMore, &[]);
assert!(term.is_terminal());
assert!(!term.is_nonterminal());
assert!(nonterm.is_nonterminal());
assert!(!nonterm.is_terminal());
assert!(!rep.is_terminal());
assert!(!rep.is_nonterminal());
}
#[test]
fn grammar_looks_up_production_by_id() {
let prod = DUMMY_GRAMMAR.production(ProductionId(0));
assert!(prod.is_some());
assert_eq!(prod.map(|p| p.name), Some("dummy_module"));
}
#[test]
fn grammar_returns_none_for_out_of_range_production() {
assert!(DUMMY_GRAMMAR.production(ProductionId(99)).is_none());
}
#[test]
fn grammar_resolves_start_production() {
let start = DUMMY_GRAMMAR.start_production();
assert!(start.is_some());
assert_eq!(start.map(|p| p.name), Some("dummy_module"));
}
#[test]
fn grammar_with_invalid_start_returns_none() {
const BROKEN: Grammar = Grammar {
name: "broken",
version: IdlVersion::V4_2,
productions: &[],
start: ProductionId(0),
token_rules: &[],
};
assert!(BROKEN.start_production().is_none());
}
#[test]
fn grammar_counts_and_iterates_productions() {
assert_eq!(DUMMY_GRAMMAR.production_count(), 1);
let names: Vec<&str> = DUMMY_GRAMMAR.productions_iter().map(|p| p.name).collect();
assert_eq!(names, vec!["dummy_module"]);
}
}