1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//! Parse and split arguments provided from a [`ParseDsl`].
//!
//! The [`args`] module is used by the [`parse_dsl_impl`] macro when generating
//! the [`ParseDsl`] implementation based on methods in the `impl` block.
//!
//! The arguments as declared in the chirp file are passed to the methods as
//! the [`MethodCtx::arguments`] field. Check the [`MethodCtx`] docs for details.
//!
//! # Internal architecture
//!
//! The actual parser implementation for the `chirp` file format is private
//! and available in the `crate::grammar` module.
//!
//! This is the classic interpreter architecture:
//!
//! > lexer → parser → AST → interpreter
//!
//! The way parsed text gets interpreted is implemented in the [`crate::interpret`]
//! module.
//!
//! [`parse_dsl_impl`]: mod@crate::parse_dsl_impl

use anyhow::Result;
use bevy::asset::LoadContext;
use bevy::reflect::TypeRegistry;
use cuicui_dsl::{BaseDsl, DslBundle};
use thiserror::Error;

pub use args::Arguments;
pub use escape::escape_literal;

mod escape;

pub mod args;

/// The input specification called a method that does not exist.
///
/// Useful as a catchall when parsing a DSL calling an innexisting method.
///
/// When encoutering this error, the interpreter uses the name span for error
/// reporting rather than the arguments span.
#[derive(Debug, Error)]
#[error("No '{method}' method")]
pub struct DslParseError {
    method: Box<str>,
}
impl DslParseError {
    /// Create a [`DslParseError`] for `method` in `parse_type`.
    pub fn new(method: impl Into<Box<str>>) -> Self {
        Self { method: method.into() }
    }
}

/// Context to run a method on [`ParseDsl::method`].
///
/// # Call format
///
/// `arguments` contain the arguments as parsed by `cuicui_chirp`. Parsing
/// removes comments and surounding spaces.
///
/// See the [`Arguments`] documentation for details.
pub struct MethodCtx<'i, 'c, 'cc> {
    // TODO(perf): Most likey could be a `[u8]` instead.
    /// The method name.
    pub name: &'i str,
    /// The method arguments.
    pub arguments: Arguments<'i, 'c>,
    /// The [`LoadContext`] used to load assets referenced in `chirp` files.
    pub ctx: Option<&'c mut LoadContext<'cc>>,
    /// The [`TypeRegistry`] the interpreter was initialized with.
    pub registry: &'c TypeRegistry,
    // TODO(perf): Consider re-using cuicui_fab::Binding
    // TODO(feat): bindings/references
}

/// A [`DslBundle`] that can be parsed.
pub trait ParseDsl: DslBundle {
    /// Apply method named `name` to `self`.
    ///
    /// # Calling format
    ///
    /// This is called with a [`MethodCtx`] argument. See the [`MethodCtx`]
    /// documentation for details as to what format to expect as argument.
    ///
    /// # Errors
    ///
    /// You may chose to fail for any reason, the expected failure case
    /// is failure to parse an argument in`ctx.args` or trying to call an
    /// innexisting method with `ctx.name`.
    ///
    /// [parent node]: cuicui_dsl::dsl#parent-node
    fn method(&mut self, ctx: MethodCtx) -> Result<()>;
}
impl ParseDsl for BaseDsl {
    fn method(&mut self, data: MethodCtx) -> Result<()> {
        let MethodCtx { name, arguments: args, .. } = data;
        if name == "named" {
            let name = args.get(0).unwrap();
            let str = String::from(String::from_utf8_lossy(name.as_ref()));
            self.named(str);
            Ok(())
        } else {
            Err(DslParseError::new(name).into())
        }
    }
}