//! Keywords for interpreting items and rules for validating them. use crate::parse::keyword::Keyword; use crate::parse::tokenize::Item; use crate::{NetdocErrorKind as EK, Result}; /// May an Item take an object? #[derive(Copy, Clone)] enum ObjKind { /// No object is allowed. NoObj, /// An object is required. RequireObj, /// An object is optional. ObjOk, } /// A set of restrictions to place on Items for a single keyword. /// /// These are built by the TokenFmtBuilder API. #[derive(Clone)] pub(crate) struct TokenFmt { /// Which keyword is being restricted? kwd: T, /// If present, a lower bound on how many arguments may be present. min_args: Option, /// If present, an upper bound on how many arguments may be present. max_args: Option, /// If true, then at least one of this Item must appear. required: bool, /// If false, then no more than one this Item may appear. may_repeat: bool, /// May this Item have an object? Must it? obj: ObjKind, } impl TokenFmt { /// Return the keyword that this rule restricts. pub(crate) fn kwd(&self) -> T { self.kwd } /// Check whether a single Item matches this TokenFmt rule, with respect /// to its number of arguments. fn item_matches_args(&self, item: &Item<'_, T>) -> Result<()> { let n_args = item.n_args(); if let Some(max) = self.max_args { if n_args > max { return Err(EK::TooManyArguments .with_msg(self.kwd.to_str()) .at_pos(item.pos())); } } if let Some(min) = self.min_args { if n_args < min { return Err(EK::TooFewArguments .with_msg(self.kwd.to_str()) .at_pos(item.pos())); } } Ok(()) } /// Check whether a single Item matches a TokenFmt rule, with respect /// to its object's presence and type. fn item_matches_obj(&self, item: &Item<'_, T>) -> Result<()> { match (&self.obj, item.has_obj()) { (ObjKind::NoObj, true) => Err(EK::UnexpectedObject .with_msg(self.kwd.to_str()) .at_pos(item.pos())), (ObjKind::RequireObj, false) => Err(EK::MissingObject .with_msg(self.kwd.to_str()) .at_pos(item.pos())), (_, _) => Ok(()), } } /// Check whether a single item has the right number of arguments /// and object. pub(crate) fn check_item(&self, item: &Item<'_, T>) -> Result<()> { self.item_matches_args(item)?; self.item_matches_obj(item) } /// Check whether this kind of item may appear this many times. pub(crate) fn check_multiplicity(&self, items: &[Item<'_, T>]) -> Result<()> { match items.len() { 0 => { if self.required { return Err(EK::MissingToken.with_msg(self.kwd.to_str())); } } 1 => (), _ => { if !self.may_repeat { return Err(EK::DuplicateToken .with_msg(self.kwd.to_str()) .at_pos(items[1].pos())); } } } Ok(()) } } /// Represents a TokenFmt under construction. /// /// To construct a rule, create this type with Keyword::rule(), then /// call method on it to set its fields, and then pass it to /// SectionRules::add(). /// /// # Example /// /// ```ignore /// // There must be exactly one "ROUTER" entry, with 5 or more arguments. /// section_rules.add(D.rule().required().args(5..)); /// ``` /// /// TODO: I'd rather have this be pub(crate), but I haven't figured out /// how to make that work. There are complicated cascading side-effects. pub struct TokenFmtBuilder(TokenFmt); impl From> for TokenFmt { fn from(builder: TokenFmtBuilder) -> Self { builder.0 } } impl TokenFmtBuilder { /// Make a new TokenFmtBuilder with default behavior. /// /// (By default, all arguments are allowed, the Item may appear 0 /// or 1 times, and it may not take an object.) pub(crate) fn new(t: T) -> Self { Self(TokenFmt { kwd: t, min_args: None, max_args: None, required: false, may_repeat: false, obj: ObjKind::NoObj, }) } /// Indicate that this Item is required. /// /// By default, no item is required. pub(crate) fn required(self) -> Self { Self(TokenFmt { required: true, ..self.0 }) } /// Indicate that this Item may appear more than once. /// /// By default, items may not repeat. pub(crate) fn may_repeat(self) -> Self { Self(TokenFmt { may_repeat: true, ..self.0 }) } /// Indicate that this Item takes no arguments. /// /// By default, items may take any number of arguments. pub(crate) fn no_args(self) -> Self { Self(TokenFmt { max_args: Some(0), ..self.0 }) } /// Indicate that this item takes a certain number of arguments. /// /// The number of arguments is provided as a range, like `5..`. pub(crate) fn args(self, r: R) -> Self where R: std::ops::RangeBounds, { use std::ops::Bound::*; let min_args = match r.start_bound() { Included(x) => Some(*x), Excluded(x) => Some(*x + 1), Unbounded => None, }; let max_args = match r.end_bound() { Included(x) => Some(*x), Excluded(x) => Some(*x - 1), Unbounded => None, }; Self(TokenFmt { min_args, max_args, ..self.0 }) } /// Indicate that this token takes an optional object. /// /// By default, objects are not allowed. pub(crate) fn obj_optional(self) -> Self { Self(TokenFmt { obj: ObjKind::ObjOk, ..self.0 }) } /// Indicate that this token takes an required object. /// /// By default, objects are not allowed. pub(crate) fn obj_required(self) -> Self { Self(TokenFmt { obj: ObjKind::RequireObj, ..self.0 }) } }