From 335c3abc67c5da89f1c85f09f7090d49859559e0 Mon Sep 17 00:00:00 2001
From: Kubat <maelle.martin@proton.me>
Date: Thu, 1 May 2025 13:09:01 +0200
Subject: [PATCH] [AST] Update AST and begin runtime implementation

- Separate the Const part of the Var in AST
- Begin to implement the SpellArgument getter (add methods to State)
- Named exports of some top-level structs/enums
---
 grimoire/src/ast/expr.rs        | 45 ++++++++++++++++---
 grimoire/src/error.rs           | 23 ++++++++--
 grimoire/src/lib.rs             | 15 ++++++-
 grimoire/src/parser/expr.rs     | 14 +++---
 grimoire/src/parser/utils.rs    |  2 +-
 grimoire/src/spell/arguments.rs | 50 ++++++++-------------
 grimoire/src/spell/factory.rs   | 12 ++---
 grimoire/src/spell/traits.rs    | 10 ++---
 grimoire/src/state.rs           | 77 ++++++++++++++++++++++++++++++++-
 grimoire/src/types.rs           | 12 +++++
 10 files changed, 198 insertions(+), 62 deletions(-)
 create mode 100644 grimoire/src/types.rs

diff --git a/grimoire/src/ast/expr.rs b/grimoire/src/ast/expr.rs
index bfd3b87..ff04c04 100644
--- a/grimoire/src/ast/expr.rs
+++ b/grimoire/src/ast/expr.rs
@@ -1,7 +1,8 @@
+use derive_more::Display;
 use crate::ast::AstSpan;
 
-#[derive(Debug, Clone, PartialEq)]
-pub enum VarOrConstant {
+#[derive(Debug, Clone, PartialEq, Display)]
+pub enum Const {
     /// Integer, like 1, 42, -1, etc
     Int(i32),
 
@@ -9,11 +10,11 @@ pub enum VarOrConstant {
     Flt(f32),
 
     /// A string, can be multiline or not: "something", '''Some other things'''
+    ///
+    /// TODO: Esapce in display!
+    #[display("'''{_0}'''")]
     Str(String),
 
-    /// A variable, just an identifier preceded by `$`, like: `$CynthiaLike`
-    Var(String),
-
     /// A boolean value, `true` or `false`
     Bool(bool),
 
@@ -21,11 +22,43 @@ pub enum VarOrConstant {
     Ident(String),
 }
 
+#[derive(Debug, Clone, PartialEq, Display)]
+pub enum VarOrConst {
+    /// A [Const].
+    Const(Const),
+
+    /// A variable, just an identifier preceded by `$`, like: `$CynthiaLike`
+    #[display("${_0}")]
+    Var(String),
+}
+
+impl VarOrConst {
+    pub fn integer(x: i32) -> Self {
+        Self::Const(Const::Int(x))
+    }
+
+    pub fn float(x: f32) -> Self {
+        Self::Const(Const::Flt(x))
+    }
+
+    pub fn boolean(x: bool) -> Self {
+        Self::Const(Const::Bool(x))
+    }
+
+    pub fn identifier(x: impl Into<String>) -> Self {
+        Self::Const(Const::Ident(x.into()))
+    }
+
+    pub fn string(x: impl Into<String>) -> Self {
+        Self::Const(Const::Str(x.into()))
+    }
+}
+
 #[derive(Debug)]
 pub enum Expression {
     Binary(AstSpan, Box<Expression>, BinOp, Box<Expression>),
     Unary(AstSpan, UnOp, Box<Expression>),
-    Leaf(AstSpan, VarOrConstant),
+    Leaf(AstSpan, VarOrConst),
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
diff --git a/grimoire/src/error.rs b/grimoire/src/error.rs
index dbe8c26..a9dbb09 100644
--- a/grimoire/src/error.rs
+++ b/grimoire/src/error.rs
@@ -1,8 +1,25 @@
+use std::{
+    num::{ParseFloatError, ParseIntError},
+    str::ParseBoolError,
+};
+
 #[derive(Debug, thiserror::Error)]
-pub enum SpellError {
-    #[error("Already defined spell '{0}': {1}")]
+pub enum Error {
+    #[error("already defined spell '{0}': {1}")]
     Redefined(&'static str, &'static str),
 
-    #[error("Undefined spell '{0}'")]
+    #[error("undefined spell '{0}'")]
     Undefined(String),
+
+    #[error("can't cast '{0}' into {1:?}")]
+    CantCast(crate::ast::expr::Const, crate::types::Type),
+
+    #[error("{0}")]
+    ParseFloat(#[from] ParseFloatError),
+
+    #[error("{0}")]
+    ParseInt(#[from] ParseIntError),
+
+    #[error("{0}")]
+    ParseBool(#[from] ParseBoolError),
 }
diff --git a/grimoire/src/lib.rs b/grimoire/src/lib.rs
index c3d4d7b..9d24904 100644
--- a/grimoire/src/lib.rs
+++ b/grimoire/src/lib.rs
@@ -1,5 +1,16 @@
+mod error;
+mod state;
+mod types;
+
 pub mod ast;
-pub mod error;
 pub mod parser;
 pub mod spell;
-pub mod state;
+
+pub mod prelude {
+    pub use crate::{
+        ast::expr::{Const as GrimoireConstant, Expression as GrimoireExpression},
+        error::Error as GrimoireError,
+        state::State as GrimoireState,
+        types::Type as GrimoireType,
+    };
+}
diff --git a/grimoire/src/parser/expr.rs b/grimoire/src/parser/expr.rs
index 33f4c0b..340f2be 100644
--- a/grimoire/src/parser/expr.rs
+++ b/grimoire/src/parser/expr.rs
@@ -16,13 +16,13 @@ fn var_or_constant(s: Span) -> ParserResult<Expression> {
     // Note: the order is important!
     utils::map_with_locaiton(
         alt((
-            map(utils::number, VarOrConstant::Int),
-            map(utils::float, VarOrConstant::Flt),
-            map(utils::string, VarOrConstant::Str),
-            value(VarOrConstant::Bool(true), utils::keyword(keywords::TRUE)),
-            value(VarOrConstant::Bool(false), utils::keyword(keywords::FALSE)),
-            map(preceded(char('$'), utils::identifier), VarOrConstant::Var),
-            map(utils::identifier, VarOrConstant::Ident),
+            map(utils::integer, VarOrConst::integer),
+            map(utils::float, VarOrConst::float),
+            map(utils::string, VarOrConst::string),
+            value(VarOrConst::boolean(true), utils::keyword(keywords::TRUE)),
+            value(VarOrConst::boolean(false), utils::keyword(keywords::FALSE)),
+            map(preceded(char('$'), utils::identifier), VarOrConst::Var),
+            map(utils::identifier, VarOrConst::identifier),
         )),
         Expression::Leaf,
     )
diff --git a/grimoire/src/parser/utils.rs b/grimoire/src/parser/utils.rs
index 5b870a8..5ebaa20 100644
--- a/grimoire/src/parser/utils.rs
+++ b/grimoire/src/parser/utils.rs
@@ -76,7 +76,7 @@ pub fn identifier(s: Span) -> ParserResult<String> {
     .parse(s)
 }
 
-pub fn number<'a>(s: Span<'a>) -> ParserResult<'a, i32> {
+pub fn integer<'a>(s: Span<'a>) -> ParserResult<'a, i32> {
     let s = multispace0(s)?.0;
     digit1
         .map_res(|s: Span<'a>| str::parse::<i32>(s.into()))
diff --git a/grimoire/src/spell/arguments.rs b/grimoire/src/spell/arguments.rs
index bf62381..35ccba3 100644
--- a/grimoire/src/spell/arguments.rs
+++ b/grimoire/src/spell/arguments.rs
@@ -1,48 +1,36 @@
-use crate::ast::expr::Expression;
+use crate::{
+    ast::expr::{Const, Expression},
+    error::Error,
+    state::State,
+    types::Type,
+};
 use std::collections::HashMap;
 
-/// Allowed types for arguments.
-#[derive(Debug)]
-pub enum ArgumentType {
-    Boolean,
-    Integer,
-    Identifier,
-    Actor,
-    Printer,
-}
-
-/// The argument.
-#[derive(Debug)]
-pub enum Argument {
-    Expression(Expression),
-    Identifier(String),
-}
-
-impl Argument {
-    pub fn into_a(&self, _into_type: ArgumentType) -> Option<Self> {
-        todo!()
-    }
-}
-
 /// The arguments that may be passed to a spell for its instanciation.
 #[derive(Debug, Default)]
-pub struct SpellArguments(HashMap<String, Argument>);
+pub struct SpellArguments(HashMap<String, Expression>);
 
 impl SpellArguments {
     /// Set/Override the value in the [Argument] list.
-    pub fn set(&mut self, name: String, value: Argument) {
-        _ = self.0.insert(name, value);
+    pub fn set(&mut self, name: impl Into<String>, value: Expression) {
+        _ = self.0.insert(name.into(), value);
     }
 
     /// Pops the value of an [Argument], and try to cast it into the asked type. On success returns
     /// [Option<Argument>::Some], otherwise [None].
-    pub fn pop(&mut self, name: impl AsRef<str>, into_type: ArgumentType) -> Option<Argument> {
-        self.0.remove(name.as_ref())?.into_a(into_type)
+    pub fn pop<S>(&mut self, state: &State, name: S, ty: Type) -> Result<Const, Error>
+    where
+        S: AsRef<str>,
+    {
+        self.0
+            .remove(name.as_ref())
+            .ok_or_else(|| Error::Undefined(name.as_ref().to_string()))
+            .map(|expr| state.evaluate_as(expr, ty))?
     }
 }
 
-impl FromIterator<(String, Argument)> for SpellArguments {
-    fn from_iter<T: IntoIterator<Item = (String, Argument)>>(iter: T) -> Self {
+impl FromIterator<(String, Expression)> for SpellArguments {
+    fn from_iter<T: IntoIterator<Item = (String, Expression)>>(iter: T) -> Self {
         Self(iter.into_iter().collect())
     }
 }
diff --git a/grimoire/src/spell/factory.rs b/grimoire/src/spell/factory.rs
index 8c9ec57..84c1281 100644
--- a/grimoire/src/spell/factory.rs
+++ b/grimoire/src/spell/factory.rs
@@ -1,11 +1,11 @@
 use crate::{
-    error::SpellError,
+    error::Error,
     spell::{BuildableSpell, Spell, SpellArguments},
 };
 use std::collections::HashMap;
 
 /// The builder function, call it with some arguments to try to build the spell
-pub type SpellBuilderFunction = fn(SpellArguments) -> Result<Box<dyn Spell>, SpellError>;
+pub type SpellBuilderFunction = fn(SpellArguments) -> Result<Box<dyn Spell>, Error>;
 
 /// The spell factory, you have to register the spell before trying to create them.
 #[derive(Debug, Default)]
@@ -21,9 +21,9 @@ impl SpellFactory {
     }
 
     /// Register a spell into the factory. In case of re-registering returns an [Err].
-    pub fn register<Sp: BuildableSpell + 'static>(&mut self) -> Result<(), SpellError> {
+    pub fn register<Sp: BuildableSpell + 'static>(&mut self) -> Result<(), Error> {
         Sp::name_list().try_for_each(|name| match self.dispatch.entry(name) {
-            std::collections::hash_map::Entry::Occupied(_) => Err(SpellError::Redefined(
+            std::collections::hash_map::Entry::Occupied(_) => Err(Error::Redefined(
                 Sp::name(),
                 "name or alias was already registered",
             )),
@@ -35,10 +35,10 @@ impl SpellFactory {
     }
 
     /// Get the builder of a [Spell] by its name. In case of undefined [Spell], returns an [Err].
-    pub fn get_builder(&self, name: impl AsRef<str>) -> Result<SpellBuilderFunction, SpellError> {
+    pub fn get_builder(&self, name: impl AsRef<str>) -> Result<SpellBuilderFunction, Error> {
         self.dispatch
             .get(name.as_ref())
             .copied()
-            .ok_or(SpellError::Undefined(name.as_ref().to_string()))
+            .ok_or(Error::Undefined(name.as_ref().to_string()))
     }
 }
diff --git a/grimoire/src/spell/traits.rs b/grimoire/src/spell/traits.rs
index c180e62..f9fa195 100644
--- a/grimoire/src/spell/traits.rs
+++ b/grimoire/src/spell/traits.rs
@@ -1,19 +1,19 @@
-use crate::{error::SpellError, spell::SpellArguments, state::State};
+use crate::{error::Error, spell::SpellArguments, state::State};
 
 /// A spell, can be casted or reverted if we need to.
 pub trait Spell {
     /// Cast the spell on a [State]. Returns a new [State].
-    fn cast(&mut self, state: State) -> Result<State, SpellError>;
+    fn cast(&mut self, state: State) -> Result<State, Error>;
 
     /// UnCast the spell from a [State]. Returns the previous [State].
-    fn uncast(&mut self, state: State) -> Result<State, SpellError>;
+    fn uncast(&mut self, state: State) -> Result<State, Error>;
 }
 
 /// A [Spell] that we can create. Is used to be able to register it into a
 /// [crate::spell::SpellFactory].
 pub trait BuildableSpell
 where
-    Self: Spell + TryFrom<SpellArguments, Error = SpellError> + 'static,
+    Self: Spell + TryFrom<SpellArguments, Error = Error> + 'static,
 {
     /// The name of the spell.
     fn name() -> &'static str;
@@ -31,7 +31,7 @@ where
     }
 
     /// Create a spell from a set of arguments. May fail.
-    fn create(arguments: SpellArguments) -> Result<Box<dyn Spell>, SpellError> {
+    fn create(arguments: SpellArguments) -> Result<Box<dyn Spell>, Error> {
         Self::try_from(arguments)
             .map(Box::new)
             .map(|boxed| boxed as Box<dyn Spell>)
diff --git a/grimoire/src/state.rs b/grimoire/src/state.rs
index 02d38c7..3fa2f57 100644
--- a/grimoire/src/state.rs
+++ b/grimoire/src/state.rs
@@ -1 +1,76 @@
-pub struct State {}
+use crate::{
+    ast::expr::{Const, Expression},
+    error::Error,
+    types::Type,
+};
+use std::collections::{HashMap, HashSet};
+
+#[derive(Debug, Default)]
+pub struct State {
+    variables: HashMap<String, Const>,
+    identifiers: HashSet<String>,
+
+    // To transform into HashMap...
+    actors: HashSet<String>,
+    printers: HashSet<String>,
+}
+
+impl State {
+    pub fn resolve(&self, var: impl AsRef<str>) -> Result<&Const, Error> {
+        self.variables
+            .get(var.as_ref())
+            .ok_or_else(|| Error::Undefined(var.as_ref().to_string()))
+    }
+
+    pub fn evaluate(&self, expr: Expression) -> Result<Const, Error> {
+        todo!()
+    }
+
+    pub fn evaluate_as(&self, expr: Expression, ty: Type) -> Result<Const, Error> {
+        match self.evaluate(expr)? {
+            Const::Int(x) => match ty {
+                Type::Float => Ok(Const::Flt(x as f32)),
+                Type::String => Ok(Const::Str(x.to_string())),
+                Type::Boolean => Ok(Const::Bool(x != 0)),
+                Type::Integer => Ok(Const::Int(x)),
+                ty => Err(Error::CantCast(Const::Int(x), ty)),
+            },
+            Const::Flt(x) => match ty {
+                Type::Float => Ok(Const::Flt(x)),
+                Type::String => Ok(Const::Str(x.to_string())),
+                Type::Boolean => Ok(Const::Bool(x.abs() <= f32::EPSILON)),
+                Type::Integer => Ok(Const::Int(x as i32)),
+                ty => Err(Error::CantCast(Const::Flt(x), ty)),
+            },
+            Const::Bool(x) => match ty {
+                Type::Float => Ok(Const::Flt(if x { 0. } else { 1. })),
+                Type::String => Ok(Const::Str(x.to_string())),
+                Type::Boolean => Ok(Const::Bool(x)),
+                Type::Integer => Ok(Const::Int(x as i32)),
+                ty => Err(Error::CantCast(Const::Bool(x), ty)),
+            },
+            Const::Str(x) => match ty {
+                Type::String => Ok(Const::Str(x)),
+                Type::Float => str::parse::<f32>(&x).map(Const::Flt).map_err(Into::into),
+                Type::Integer => str::parse::<i32>(&x).map(Const::Int).map_err(Into::into),
+                Type::Boolean => str::parse::<bool>(&x).map(Const::Bool).map_err(Into::into),
+                Type::Identifier | Type::Actor | Type::Printer => unreachable!(),
+            },
+            Const::Ident(x) => match ty {
+                Type::Identifier => match self.identifiers.contains(&x) {
+                    true => Ok(Const::Ident(x)),
+                    false => Err(Error::Undefined(x)),
+                },
+                Type::Actor => match self.actors.contains(&x) {
+                    true => Ok(Const::Ident(x)), // TODO: Return the Actor object?
+                    false => Err(Error::Undefined(x)),
+                },
+                Type::Printer => match self.printers.contains(&x) {
+                    true => Ok(Const::Ident(x)), // TODO: Return the Printer object?
+                    false => Err(Error::Undefined(x)),
+                },
+                ty => Err(Error::CantCast(Const::Ident(x), ty)),
+            },
+        }
+    }
+}
diff --git a/grimoire/src/types.rs b/grimoire/src/types.rs
new file mode 100644
index 0000000..da91ab8
--- /dev/null
+++ b/grimoire/src/types.rs
@@ -0,0 +1,12 @@
+/// Allowed types for arguments.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Type {
+    Float,
+    String,
+    Integer,
+    Boolean,
+
+    Identifier,
+    Actor,
+    Printer,
+}
-- 
GitLab