nyandere 0.1.2

i help with keeping track of purchases. meow
Documentation
//! # General structure
//!
//! Nyandere is a *language*.
//! A language generally consists of:
//!
//! **Part**    | **Description**                                | **Implementation**
//! ------------|------------------------------------------------|-------------------
//! *Alphabet*  | The *building blocks* of structures/sentences. | [`syntax::lex`]
//! *Syntax*    | How structures are *formed* and look like.     | [`syntax`]
//! *Semantics* | What built structures *mean*.                  | Rest of this crate
//!
//! ## Why the distinction?
//!
//! In short, while syntax is concerned with accurately representing
//! the source code via types, it doesn't cover the actual meaning.
//! Semantics on the other hand encode what makes *sense*.
//! This is not automatically given.
//!
//! ### Example: Valid syntax, semantically meaningful
//!
//! ```text
//! create entity A      # (1)
//! create entity B      # (2)
//! balance from=A to=B  # (3)
//! ```
//!
//! While `(1)` and `(2)` don't rely on past state, `(3)` does:
//! Entities `A` and `B` must exist for the command to be valid.
//! In the above case that is given: Both `A` and `B` exist and are entities.
//!
//! ### Example: Valid syntax, semantically nonsense
//!
//! ```text
//! balance from=C to=D  # (4)
//! ```
//!
//! Neither entity `C` nor entity `D` exist!
//! Hence, while this is *syntactically* valid,
//! it is *semantically* invalid!
//! More concretely:
//! While [`syntax`] **is** able to encode `(4)`,
//! it is **not** possible to create a [`runtime::cmd`] of `(4)`
//! without having `C` and `D` defined first.[^1]
//!
//! [^1]: If you do find a way, that is a bug and should be fixed.
#![doc = concat!("Please report it at <", env!("CARGO_PKG_REPOSITORY"), ">!")]
//!
//! # Pipeline
//!
//! The general processing pipeline is:
//!
//! 1. Load source code as a string.
//! 2. Parse string into a [`Script`] using [`syntax::parse`].
//!     - [`Script`] serves as the AST root
//! 3. Run the [`Script`] in the [`Runtime`] using [`Runtime::run`]
//!
//! # Adding new commands
//!
//! Task list for adding a new command `nya`:
//!
//! ## 1. Syntax
//!
//! 1. Think of one (where is it used? what are its arguments?)
//! 2. Update `doc/syntax.abnf` appropriately
//! 3. Expand the AST in [`syntax::ast`]
//!    to include `nya` below [`Stmt`][syntax::ast::Stmt]
//!    in the tree
//! 4. Parse it in [`syntax::parse`]
//!
//! ## 2. Semantics
//!
//! 1. Add a submodule `nya` in [`runtime::cmd`] for the command
//! 2. In there, write a type `Nya` with the type-restricted fields for the command
//! 3. Implement [`runtime::cmd::Command`] for `Nya`
//!
//! ## 3. Testing
//!
//! Try to cover both edge cases and typical use cases.

#[macro_use]
extern crate macro_rules_attribute;

pub type Map<K, V> = std::collections::BTreeMap<K, V>;
pub type Set<T> = std::collections::HashSet<T>;
pub type Name = String;
pub type NameRef<'name> = &'name str;

pub mod aux;
pub mod error;
pub mod ext;
pub mod runtime;
pub mod syntax;

use ext::config;
pub use runtime::Runtime;

use eyre::{Result, WrapErr, format_err};
use syntax::ast::Script;

pub fn run() -> Result<()> {
    let cfg = config::cli();
    let script = cfg.source.get().wrap_err("while loading source")?;
    eval(script)?;

    Ok(())
}

/// Parses and runs the given script,
/// returning the final runtime state.
pub fn eval(script: impl AsRef<str>) -> Result<Runtime> {
    let script = Script::parse(script.as_ref())
        .into_result()
        .map_err(|orig| format_err!("while parsing source code: {orig:?}"))?;

    let mut runtime = Runtime::new();
    runtime.run(script).unwrap();

    Ok(runtime)
}

#[cfg(test)]
pub fn one(rt: &mut Runtime, src: &str) -> String {
    let stmt = Script::parse(src).unwrap().0[0].clone();
    let cmd = runtime::cmd::construct(stmt, rt).unwrap();
    rt.step(cmd).map(|out| out.to_string()).unwrap_or_default()
}

#[cfg(test)]
mod tests {
    use crate::runtime::Resolve;

    use super::*;

    #[test]
    #[ignore = "braces syntax not supported yet"]
    fn smoke() {
        let rt = eval(include_str!("../asset/examples/typical.nyan")).unwrap();
        let _dir = ("A", "B").resolve(&rt).unwrap();
        todo!();
        //assert_eq!(dir.resolve(&rt).unwrap(), Balance(Integer::from(-200)));
    }
}