summaryrefslogtreecommitdiff
path: root/src/func/args.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/func/args.rs')
-rw-r--r--src/func/args.rs215
1 files changed, 215 insertions, 0 deletions
diff --git a/src/func/args.rs b/src/func/args.rs
new file mode 100644
index 00000000..2e9e80cc
--- /dev/null
+++ b/src/func/args.rs
@@ -0,0 +1,215 @@
+//! Parsing, storing and deduplication of function arguments.
+
+use super::prelude::*;
+use Expression::*;
+
+/// Provides a convenient interface to parse the arguments to a function.
+pub struct ArgParser<'a> {
+ args: &'a FuncArgs,
+ positional_index: usize,
+}
+
+impl<'a> ArgParser<'a> {
+ pub fn new(args: &'a FuncArgs) -> ArgParser<'a> {
+ ArgParser {
+ args,
+ positional_index: 0,
+ }
+ }
+
+ /// Get the next positional argument of the given type.
+ ///
+ /// If there are no more arguments or the type is wrong,
+ /// this will return an error.
+ pub fn get_pos<T>(&mut self) -> ParseResult<Spanned<T::Output>>
+ where T: Argument<'a> {
+ Self::expected(self.get_pos_opt::<T>()?)
+ }
+
+ /// Get the next positional argument if there is any.
+ ///
+ /// If the argument is of the wrong type, this will return an error.
+ pub fn get_pos_opt<T>(&mut self) -> ParseResult<Option<Spanned<T::Output>>>
+ where T: Argument<'a> {
+ let arg = self.args.positional
+ .get(self.positional_index)
+ .map(T::from_expr)
+ .transpose();
+
+ if let Ok(Some(_)) = arg {
+ self.positional_index += 1;
+ }
+
+ arg
+ }
+
+ /// Get a keyword argument with the given key and type.
+ pub fn get_key<T>(&mut self, key: &str) -> ParseResult<Spanned<T::Output>>
+ where T: Argument<'a> {
+ Self::expected(self.get_key_opt::<T>(key)?)
+ }
+
+ /// Get a keyword argument with the given key and type if it is present.
+ pub fn get_key_opt<T>(&mut self, key: &str) -> ParseResult<Option<Spanned<T::Output>>>
+ where T: Argument<'a> {
+ self.args.keyword.iter()
+ .find(|entry| entry.val.0.val == key)
+ .map(|entry| T::from_expr(&entry.val.1))
+ .transpose()
+ }
+
+ /// Assert that there are no positional arguments left. Returns an error
+ /// otherwise.
+ pub fn done(&self) -> ParseResult<()> {
+ if self.positional_index == self.args.positional.len() {
+ Ok(())
+ } else {
+ pr!("unexpected argument");
+ }
+ }
+
+ /// Covert an option to a result with an error on `None`.
+ fn expected<T>(val: Option<Spanned<T::Output>>) -> ParseResult<Spanned<T::Output>>
+ where T: Argument<'a> {
+ val.ok_or_else(|| pr!(@"expected {}", T::ERROR_MESSAGE))
+ }
+}
+
+/// A kind of argument.
+pub trait Argument<'a> {
+ type Output;
+ const ERROR_MESSAGE: &'static str;
+
+ fn from_expr(expr: &'a Spanned<Expression>) -> ParseResult<Spanned<Self::Output>>;
+}
+
+macro_rules! arg {
+ ($type:ident, $err:expr, $doc:expr, $output:ty, $wanted:pat => $converted:expr) => (
+ #[doc = $doc]
+ #[doc = " argument for use with the [`ArgParser`]."]
+ pub struct $type;
+ impl<'a> Argument<'a> for $type {
+ type Output = $output;
+ const ERROR_MESSAGE: &'static str = $err;
+
+ fn from_expr(expr: &'a Spanned<Expression>) -> ParseResult<Spanned<Self::Output>> {
+ #[allow(unreachable_patterns)]
+ match &expr.val {
+ $wanted => Ok(Spanned::new($converted, expr.span)),
+ _ => pr!("expected {}", $err),
+ }
+ }
+ }
+ );
+}
+
+arg!(ArgExpr, "expression", "A generic expression", &'a Expression, expr => &expr);
+arg!(ArgIdent, "identifier", "An identifier (e.g. `horizontal`)", &'a str, Ident(s) => s.as_str());
+arg!(ArgStr, "string", "A string (e.g. `\"Hello\"`)", &'a str, Str(s) => s.as_str());
+arg!(ArgNum, "number", "A number (e.g. `5.4`)", f64, Num(n) => *n);
+arg!(ArgSize, "size", "A size (e.g. `12pt`)", crate::size::Size, Size(s) => *s);
+arg!(ArgBool, "bool", "A boolean (`true` or `false`)", bool, Bool(b) => *b);
+
+/// An argument key which identifies a layouting axis.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum AxisKey {
+ Primary,
+ Secondary,
+ Vertical,
+ Horizontal,
+}
+
+impl AxisKey {
+ /// The generic version of this axis key in the given system of axes.
+ pub fn generic(&self, axes: LayoutAxes) -> GenericAxisKind {
+ match self {
+ Primary => GenericAxisKind::Primary,
+ Secondary => GenericAxisKind::Secondary,
+ Vertical => axes.vertical(),
+ Horizontal => axes.horizontal(),
+ }
+ }
+
+ /// The specific version of this axis key in the given system of axes.
+ pub fn specific(&self, axes: LayoutAxes) -> SpecificAxisKind {
+ match self {
+ Primary => axes.primary(),
+ Secondary => axes.secondary(),
+ Vertical => SpecificAxisKind::Vertical,
+ Horizontal => SpecificAxisKind::Horizontal,
+ }
+ }
+}
+
+/// An argument key which identifies a target alignment.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum AlignmentKey {
+ Left,
+ Top,
+ Right,
+ Bottom,
+ Origin,
+ Center,
+ End,
+}
+
+impl AlignmentKey {
+ /// The generic axis this alignment key corresopnds to in the given system
+ /// of layouting axes. Falls back to `default` if the alignment is generic.
+ pub fn axis(&self, axes: LayoutAxes, default: GenericAxisKind) -> GenericAxisKind {
+ use AlignmentKey::*;
+ match self {
+ Origin | Center | End => default,
+ Left | Right => axes.horizontal(),
+ Top | Bottom => axes.vertical(),
+ }
+ }
+
+ /// The generic version of this alignment in the given system of layouting
+ /// axes. Returns an error if the alignment is invalid for the given axis.
+ pub fn generic(&self, axes: LayoutAxes, axis: GenericAxisKind) -> LayoutResult<Alignment> {
+ use AlignmentKey::*;
+
+ let horizontal = axis == axes.horizontal();
+ Ok(match self {
+ Origin => Alignment::Origin,
+ Center => Alignment::Center,
+ End => Alignment::End,
+ Left if horizontal => axes.left(),
+ Right if horizontal => axes.right(),
+ Top if !horizontal => axes.top(),
+ Bottom if !horizontal => axes.bottom(),
+ _ => lr!(
+ "invalid alignment `{}` for {} axis",
+ format!("{:?}", self).to_lowercase(),
+ format!("{:?}", axis).to_lowercase()
+ )
+ })
+ }
+
+ /// The specific version of this alignment in the given system of layouting
+ /// axes.
+ pub fn specific(&self, axes: LayoutAxes, axis: SpecificAxisKind) -> AlignmentKey {
+ use AlignmentKey::*;
+ match (self, axis) {
+ (Origin, SpecificAxisKind::Horizontal) => Left,
+ (End, SpecificAxisKind::Horizontal) => Right,
+ (Origin, SpecificAxisKind::Vertical) => Top,
+ (End, SpecificAxisKind::Vertical) => Bottom,
+ _ => *self,
+ }
+ }
+}
+
+/// An argument key which identifies a margin or padding target.
+///
+/// A is the axis type used.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum PaddingKey<A> {
+ /// All four sides should have the specified padding.
+ All,
+ /// Both sides of the given axis should have the specified padding.
+ Axis(A),
+ /// Only the given side of the given axis should have the specified padding.
+ AxisAligned(A, AlignmentKey),
+}