Repository: CSML-by-Clevy/csml-engine (rust)

Issue:
forget memory
**Is your feature request related to a problem? Please describe.**
currently there is no easy way to delete client memories, the only option available is replacing by null 
example: do var = null

**Describe the solution you'd like**
add a new keyword 'forget'

forget * // all memories
forget tutu // single memory
forget [tutu, tata, toto] // list of memories

Pull request:
feat: forget memory
Fixes #284 

## Proposed Changes

- add new command 'forget'


Patch:
diff --git a/csml_engine/src/interpreter_actions.rs b/csml_engine/src/interpreter_actions.rs
index 8b6e25e8..cd14fd0b 100644
--- a/csml_engine/src/interpreter_actions.rs
+++ b/csml_engine/src/interpreter_actions.rs
@@ -1,11 +1,11 @@
-use crate::data::*;
+use crate::{data::*, delete_client_memories};
 use crate::db_connectors::{
     conversations::*, interactions::*, memories::*, messages::*, nodes::*, state::*,
 };
 use crate::utils::*;
 
 use csml_interpreter::{
-    data::{csml_bot::CsmlBot, csml_flow::CsmlFlow, Event, Hold, MSG},
+    data::{ast::ForgetMemory, csml_bot::CsmlBot, csml_flow::CsmlFlow, Event, Hold, MSG},
     interpret,
 };
 use std::collections::HashMap;
@@ -39,9 +39,27 @@ pub fn interpret_step(
 
     for received in receiver {
         match received {
-            MSG::Memory(mem) => {
+            MSG::Remember(mem) => {
                 memories.insert(mem.key.clone(), mem);
             },
+            MSG::Forget(mem) => {
+                match mem {
+                    ForgetMemory::ALL => {
+                        memories.clear();
+                        delete_client_memories(&data.client)?;
+                    },
+                    ForgetMemory::SINGLE(memory) => {
+                        memories.remove(&memory.ident);
+                        crate::delete_client_memory(&data.client, &memory.ident)?;
+                    }
+                    ForgetMemory::LIST(mem_list) => {
+                        for mem in mem_list.iter() {
+                            memories.remove(&mem.ident);
+                            crate::delete_client_memory(&data.client, &mem.ident)?;
+                        }
+                    }
+                }
+            },
             MSG::Message(msg) => {
                 send_msg_to_callback_url(data, vec![msg.clone()], interaction_order, false);
                 data.messages.push(msg);
diff --git a/csml_interpreter/CSML/examples/hello_world.csml b/csml_interpreter/CSML/examples/hello_world.csml
index 7649d614..342051db 100644
--- a/csml_interpreter/CSML/examples/hello_world.csml
+++ b/csml_interpreter/CSML/examples/hello_world.csml
@@ -1,9 +1,5 @@
 start:
 
-    // say Url("Hello World 😆")
-    say Time().at(2020, 12, 24)
-
-    do x = Time().at(2020, 12, 24)
-    say "{{x}}"
+    say "Hello World 😆"
 
     goto end
diff --git a/csml_interpreter/src/data.rs b/csml_interpreter/src/data.rs
index 9403c96a..572e751a 100644
--- a/csml_interpreter/src/data.rs
+++ b/csml_interpreter/src/data.rs
@@ -35,4 +35,4 @@ pub use message::Message;
 pub use message_data::MessageData;
 pub use position::Position;
 
-pub use msg::MSG;
+pub use msg::{MSG};
diff --git a/csml_interpreter/src/data/ast.rs b/csml_interpreter/src/data/ast.rs
index cd11aa36..423bdaaf 100644
--- a/csml_interpreter/src/data/ast.rs
+++ b/csml_interpreter/src/data/ast.rs
@@ -153,6 +153,13 @@ pub struct Function {
     pub args: Box<Expr>,
 }
 
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub enum ForgetMemory {
+    ALL,
+    SINGLE(Identifier),
+    LIST(Vec<Identifier>)
+}
+
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub enum ObjectType {
     Goto(GotoType, Interval),
@@ -165,6 +172,7 @@ pub enum ObjectType {
 
     Remember(Identifier, Box<Expr>),
     Assign(Box<Expr>, Box<Expr>),
+    Forget(ForgetMemory, Interval),
 
     As(Identifier, Box<Expr>),
 
diff --git a/csml_interpreter/src/data/msg.rs b/csml_interpreter/src/data/msg.rs
index 0320d733..95a092e8 100644
--- a/csml_interpreter/src/data/msg.rs
+++ b/csml_interpreter/src/data/msg.rs
@@ -1,6 +1,8 @@
-use crate::data::error_info::ErrorInfo;
-use crate::data::primitive::PrimitiveNull;
-use crate::data::{hold::Hold, message::Message, Literal, Memory, MessageData};
+use crate::data::{
+    ast::ForgetMemory, primitive::PrimitiveNull,
+    hold::Hold, message::Message, Literal, Memory, MessageData,
+    error_info::ErrorInfo
+};
 
 use std::sync::mpsc;
 
@@ -10,7 +12,8 @@ use std::sync::mpsc;
 
 #[derive(Debug)]
 pub enum MSG {
-    Memory(Memory),
+    Remember(Memory),
+    Forget(ForgetMemory),
     Message(Message),
     Hold(Hold),
     Next {
diff --git a/csml_interpreter/src/data/tokens.rs b/csml_interpreter/src/data/tokens.rs
index 258ab100..c20d458b 100644
--- a/csml_interpreter/src/data/tokens.rs
+++ b/csml_interpreter/src/data/tokens.rs
@@ -72,6 +72,7 @@ pub const MATCH: &str = "match";
 pub const NOT_MATCH: &str = "!match";
 pub const DEFAULT: &str = "default";
 pub const REMEMBER: &str = "remember";
+pub const FORGET: &str = "forget";
 pub const _METADATA: &str = "_metadata";
 pub const _MEMORY: &str = "_memory";
 pub const _ENV: &str = "_env";
@@ -79,7 +80,7 @@ pub const BREAK: &str = "break";
 pub const CONTINUE: &str = "continue";
 pub const RETURN: &str = "return";
 
-pub const FN_SCOPE_REJECTED: &[&str] = &[SAY, GOTO, REMEMBER, USE, HOLD, BREAK];
+pub const FN_SCOPE_REJECTED: &[&str] = &[SAY, GOTO, REMEMBER, FORGET, USE, HOLD, BREAK];
 
 pub const TRUE: &str = "true";
 pub const FALSE: &str = "false";
@@ -108,17 +109,17 @@ pub const TYPES: &[&str] = &[
 
 pub const RESERVED: &[&str] = &[
     FOREACH, IF, ELSE, IMPORT, AS, IN, DO, FROM, EVENT, FLOW, FILE, STEP, SAY, USE, HOLD, GOTO,
-    MATCH, _METADATA, _MEMORY, _ENV, DEFAULT, REMEMBER, TRUE, FALSE, NULL, BREAK, COMPONENT,
+    MATCH, _METADATA, _MEMORY, _ENV, DEFAULT, REMEMBER, FORGET, TRUE, FALSE, NULL, BREAK, COMPONENT,
 ];
 
 pub const UTILISATION_RESERVED: &[&str] = &[
-    FOREACH, IF, ELSE, IMPORT, AS, DO, FLOW, STEP, SAY, USE, HOLD, GOTO, MATCH, REMEMBER, BREAK,
+    FOREACH, IF, ELSE, IMPORT, AS, DO, FLOW, STEP, SAY, USE, HOLD, GOTO, MATCH, REMEMBER, FORGET, BREAK,
     COMPONENT,
 ];
 
 pub const ASSIGNATION_RESERVED: &[&str] = &[
     FOREACH, IF, ELSE, IMPORT, AS, DO, EVENT, FLOW, STEP, SAY, USE, HOLD, GOTO, MATCH, REMEMBER,
-    _METADATA, _MEMORY, _ENV, TRUE, FALSE, NULL, BREAK, COMPONENT,
+    FORGET,  _METADATA, _MEMORY, _ENV, TRUE, FALSE, NULL, BREAK, COMPONENT,
 ];
 
 pub const TYPING: &str = "Typing";
diff --git a/csml_interpreter/src/interpreter/ast_interpreter/actions.rs b/csml_interpreter/src/interpreter/ast_interpreter/actions.rs
index 9e3fc435..f8dc7053 100644
--- a/csml_interpreter/src/interpreter/ast_interpreter/actions.rs
+++ b/csml_interpreter/src/interpreter/ast_interpreter/actions.rs
@@ -5,12 +5,12 @@ use crate::data::{
     literal::ContentType,
     message::*,
     primitive::{closure::capture_variables, PrimitiveNull},
-    Literal, Memory, MemoryType, MessageData, MSG,
+    Literal, Memory, MemoryType, MessageData, MSG
 };
 use crate::error_format::*;
 use crate::interpreter::variable_handler::{
     exec_path_actions, expr_to_literal, get_var_from_mem, interval::*, memory::*, resolve_fn_args,
-    search_goto_var_memory,
+    search_goto_var_memory, forget_memories::{forget_scope_memories, remove_message_data_memories}
 };
 use crate::parser::ExitCondition;
 use std::sync::mpsc;
@@ -226,7 +226,7 @@ pub fn match_actions(
 
             MSG::send(
                 &sender,
-                MSG::Memory(Memory::new(name.ident.to_owned(), new_value.clone())),
+                MSG::Remember(Memory::new(name.ident.to_owned(), new_value.clone())),
             );
 
             data.context
@@ -234,6 +234,20 @@ pub fn match_actions(
                 .insert(name.ident.to_owned(), new_value);
             Ok(msg_data)
         }
+        ObjectType::Forget(memory, _interval) => {
+            // delete memories form message data
+            remove_message_data_memories(&memory, &mut msg_data);
+            // delete memory from current scope
+            forget_scope_memories(&memory, data);
+
+            MSG::send(
+                &sender,
+                MSG::Forget(memory.to_owned()),
+            );
+
+            Ok(msg_data)
+        }
+
         reserved => Err(gen_error_info(
             Position::new(interval_from_reserved_fn(reserved), &data.context.flow),
             ERROR_START_INSTRUCTIONS.to_owned(),
diff --git a/csml_interpreter/src/interpreter/variable_handler.rs b/csml_interpreter/src/interpreter/variable_handler.rs
index 2bdef852..f2a1401a 100644
--- a/csml_interpreter/src/interpreter/variable_handler.rs
+++ b/csml_interpreter/src/interpreter/variable_handler.rs
@@ -6,6 +6,7 @@ pub mod match_literals;
 pub mod memory;
 pub mod operations;
 pub mod resolve_csml_object;
+pub mod forget_memories;
 
 use crate::data::literal::ContentType;
 pub use expr_to_literal::{expr_to_literal, resolve_fn_args};
diff --git a/csml_interpreter/src/interpreter/variable_handler/forget_memories.rs b/csml_interpreter/src/interpreter/variable_handler/forget_memories.rs
new file mode 100644
index 00000000..01121c40
--- /dev/null
+++ b/csml_interpreter/src/interpreter/variable_handler/forget_memories.rs
@@ -0,0 +1,62 @@
+use crate::data::{
+    ast::*, data::Data, MessageData
+};
+
+pub fn forget_scope_memories(forget_mem: &ForgetMemory, data: &mut Data) {
+    match forget_mem {
+        ForgetMemory::ALL => {
+            data.step_vars.clear();
+            data.context.current.clear();
+        },
+        ForgetMemory::SINGLE(memory) => {
+            data.step_vars.remove(&memory.ident);
+            data.context.current.remove(&memory.ident);
+        },
+        ForgetMemory::LIST(memories) => {
+            for memory in memories.iter() {
+                data.step_vars.remove(&memory.ident);
+                data.context.current.remove(&memory.ident);
+            }
+        },
+    }
+}
+
+pub fn remove_message_data_memories(forget_mem: &ForgetMemory, message_data: &mut MessageData) {
+    match forget_mem {
+        ForgetMemory::ALL => {
+            message_data.memories = None;
+        },
+        ForgetMemory::SINGLE(memory) => {
+            if let Some(memories_to_remember) = &message_data.memories {
+                let memories =
+                    memories_to_remember.iter()
+                            .fold(vec![], |mut acc, mem| {
+                                    if mem.key != memory.ident {
+                                        acc.push(mem.to_owned());
+                                    }
+                                    acc
+                                }
+                            );
+
+                message_data.memories = Some(memories);
+            }
+        },
+        ForgetMemory::LIST(memories) => {
+            let list_of_memories: Vec<String> = memories.iter().map(|mem| mem.ident.to_owned()).collect();
+
+            if let Some(memories_to_remember) = &message_data.memories {
+                let memories =
+                    memories_to_remember.iter()
+                            .fold(vec![], |mut acc, mem| {
+                                    if !list_of_memories.contains(&mem.key) {
+                                        acc.push(mem.to_owned());
+                                    }
+                                    acc
+                                }
+                            );
+
+                message_data.memories = Some(memories);
+            }
+        },
+    }
+}
\ No newline at end of file
diff --git a/csml_interpreter/src/interpreter/variable_handler/interval.rs b/csml_interpreter/src/interpreter/variable_handler/interval.rs
index b1ddcc92..c2a31c93 100644
--- a/csml_interpreter/src/interpreter/variable_handler/interval.rs
+++ b/csml_interpreter/src/interpreter/variable_handler/interval.rs
@@ -39,6 +39,7 @@ pub fn interval_from_reserved_fn(reserved_fn: &ObjectType) -> Interval {
         ObjectType::Debug(_expr, interval) => interval.to_owned(),
         ObjectType::Return(expr) => interval_from_expr(expr),
         ObjectType::Remember(ident, ..) => ident.interval.to_owned(),
+        ObjectType::Forget(_, interval) => interval.to_owned(),
         ObjectType::Assign(ident, ..) => interval_from_expr(ident),
         ObjectType::As(ident, ..) => ident.interval.to_owned(),
         ObjectType::BuiltIn(Function { interval, .. }) => interval.to_owned(),
diff --git a/csml_interpreter/src/interpreter/variable_handler/memory.rs b/csml_interpreter/src/interpreter/variable_handler/memory.rs
index ed934726..cfde5b47 100644
--- a/csml_interpreter/src/interpreter/variable_handler/memory.rs
+++ b/csml_interpreter/src/interpreter/variable_handler/memory.rs
@@ -48,7 +48,7 @@ pub fn save_literal_in_mem(
             // save new value in current memory
             msg_data.add_to_memory(&name, lit.clone());
             // send new value to manager in order to be save in db
-            MSG::send(sender, MSG::Memory(Memory::new(name.clone(), lit.clone())));
+            MSG::send(sender, MSG::Remember(Memory::new(name.clone(), lit.clone())));
             data.context.current.insert(name, lit);
         }
         MemoryType::Use if update => {
diff --git a/csml_interpreter/src/parser/parse_actions.rs b/csml_interpreter/src/parser/parse_actions.rs
index 722900bf..0e99a828 100644
--- a/csml_interpreter/src/parser/parse_actions.rs
+++ b/csml_interpreter/src/parser/parse_actions.rs
@@ -7,12 +7,22 @@ use crate::parser::{
     parse_comments::comment,
     parse_foreach::parse_foreach,
     parse_goto::parse_goto,
-    parse_idents::parse_idents_assignation,
+    parse_idents::{parse_idents_assignation, parse_idents_usage},
     parse_if::parse_if,
     parse_path::parse_path,
+    parse_var_types::parse_r_bracket,
     tools::{get_interval, get_string, get_tag},
 };
-use nom::{branch::alt, bytes::complete::tag, error::*, sequence::preceded, Err, IResult};
+
+use nom::{
+    branch::alt,
+    bytes::complete::tag,
+    combinator::{opt},
+    error::ParseError,
+    multi::separated_list,
+    sequence::{preceded, terminated, tuple},
+    Err, IResult,
+};
 
 ////////////////////////////////////////////////////////////////////////////////
 // TOOL FUNCTIONS
@@ -57,6 +67,42 @@ where
     }
 }
 
+fn parse_forget_all<'a, E>(s: Span<'a>) -> IResult<Span<'a>, ForgetMemory, E>
+where
+    E: ParseError<Span<'a>>,
+{
+    let (s, _) = tag("*")(s)?;
+
+    Ok((s, ForgetMemory::ALL))
+}
+
+fn parse_forget_single<'a, E>(s: Span<'a>) -> IResult<Span<'a>, ForgetMemory, E>
+where
+    E: ParseError<Span<'a>>,
+{
+    let (s, ident) = parse_idents_usage(s)?;
+
+    Ok((s, ForgetMemory::SINGLE(ident)))
+}
+
+fn parse_forget_list<'a, E>(s: Span<'a>) -> IResult<Span<'a>, ForgetMemory, E>
+where
+    E: ParseError<Span<'a>>,
+{
+    let (s, (vec, _)) = preceded(
+        tag(L_BRACKET),
+        terminated(
+            tuple((
+                separated_list(preceded(comment, tag(COMMA)), parse_idents_usage), 
+                opt(preceded(comment, tag(COMMA))),
+            )),
+            preceded(comment, parse_r_bracket),
+        ),
+    )(s)?;
+
+    Ok((s, ForgetMemory::LIST(vec)))
+}
+
 fn parse_action_argument<'a, E, F, G>(s: Span<'a>, func: F) -> IResult<Span<'a>, G, E>
 where
     E: ParseError<Span<'a>>,
@@ -115,6 +161,31 @@ where
     ))
 }
 
+fn parse_forget<'a, E>(s: Span<'a>) -> IResult<Span<'a>, Expr, E>
+where
+    E: ParseError<Span<'a>>,
+{
+    let (s, name) = preceded(comment, get_string)(s)?;
+    let (s, interval) = get_interval(s)?;
+    let (s, ..) = get_tag(name, FORGET)(s)?;
+
+    let (s, forget_mem) = parse_action_argument(s,
+        preceded(
+            comment,
+            alt((
+                parse_forget_all,
+                parse_forget_single,
+                parse_forget_list
+            ))
+        )
+    )?;
+
+    Ok((
+        s,
+        Expr::ObjectExpr(ObjectType::Forget(forget_mem, interval)),
+    ))
+}
+
 fn parse_say<'a, E>(s: Span<'a>) -> IResult<Span<'a>, Expr, E>
 where
     E: ParseError<Span<'a>>,
@@ -258,6 +329,7 @@ where
         parse_goto,
         parse_say,
         parse_remember,
+        parse_forget,
         parse_hold,
         // only accessible in functions scopes
         parse_return,
diff --git a/csml_interpreter/src/parser/parse_var_types.rs b/csml_interpreter/src/parser/parse_var_types.rs
index 75c573ee..65bc6b2f 100644
--- a/csml_interpreter/src/parser/parse_var_types.rs
+++ b/csml_interpreter/src/parser/parse_var_types.rs
@@ -28,19 +28,6 @@ use nom::{
 // PRIVATE FUNCTIONS
 ////////////////////////////////////////////////////////////////////////////////
 
-fn parse_r_bracket<'a, E>(s: Span<'a>) -> IResult<Span<'a>, Span<'a>, E>
-where
-    E: ParseError<Span<'a>>,
-{
-    match tag(R_BRACKET)(s) {
-        Ok((rest, val)) => Ok((rest, val)),
-        Err(Err::Error((s, _err))) | Err(Err::Failure((s, _err))) => {
-            Err(gen_nom_failure(s, ERROR_RIGHT_BRACKET))
-        }
-        Err(Err::Incomplete(needed)) => Err(Err::Incomplete(needed)),
-    }
-}
-
 fn parse_condition_group<'a, E: ParseError<Span<'a>>>(s: Span<'a>) -> IResult<Span<'a>, Expr, E>
 where
     E: ParseError<Span<'a>>,
@@ -73,6 +60,19 @@ where
 // PUBLIC FUNCTIONS
 ////////////////////////////////////////////////////////////////////////////////
 
+pub fn parse_r_bracket<'a, E>(s: Span<'a>) -> IResult<Span<'a>, Span<'a>, E>
+where
+    E: ParseError<Span<'a>>,
+{
+    match tag(R_BRACKET)(s) {
+        Ok((rest, val)) => Ok((rest, val)),
+        Err(Err::Error((s, _err))) | Err(Err::Failure((s, _err))) => {
+            Err(gen_nom_failure(s, ERROR_RIGHT_BRACKET))
+        }
+        Err(Err::Incomplete(needed)) => Err(Err::Incomplete(needed)),
+    }
+}
+
 pub fn parse_idents_expr_usage<'a, E>(s: Span<'a>) -> IResult<Span<'a>, Expr, E>
 where
     E: ParseError<Span<'a>>,


Test patch:
diff --git a/csml_interpreter/CSML/basic_test/syntax/forget.csml b/csml_interpreter/CSML/basic_test/syntax/forget.csml
new file mode 100644
index 00000000..d5a13070
--- /dev/null
+++ b/csml_interpreter/CSML/basic_test/syntax/forget.csml
@@ -0,0 +1,28 @@
+start:
+    goto end
+
+forget_all:
+    do mem = 42
+    remember toto = "text"
+
+    forget *
+
+    goto end
+
+forget_single:
+    do mem = 42
+    remember toto = "text"
+
+    forget toto
+
+    say _memory
+
+    goto end
+
+forget_list:
+    do mem = 42
+    remember toto = "text"
+
+    forget [toto, mem]
+
+    goto end
\ No newline at end of file
diff --git a/csml_interpreter/tests/forget.rs b/csml_interpreter/tests/forget.rs
new file mode 100644
index 00000000..7c588963
--- /dev/null
+++ b/csml_interpreter/tests/forget.rs
@@ -0,0 +1,83 @@
+mod support;
+
+use csml_interpreter::data::context::Context;
+use csml_interpreter::data::{event::Event, primitive::PrimitiveInt, Interval};
+use std::collections::HashMap;
+
+use crate::support::tools::format_message;
+use crate::support::tools::message_to_json_value;
+
+use serde_json::Value;
+
+#[test]
+fn forget_all() {
+    let data = r#"{
+        "memories":[],
+        "messages":[]
+    }"#;
+    let mut metadata = HashMap::new();
+    metadata.insert(
+        "var".to_owned(),
+        PrimitiveInt::get_literal(42, Interval::default()),
+    );
+
+    let msg = format_message(
+        Event::new("payload", "", serde_json::json!({})),
+        Context::new(HashMap::new(), metadata, None, None, "forget_all", "flow"),
+        "CSML/basic_test/syntax/forget.csml",
+    );
+
+    let v1: Value = message_to_json_value(msg);
+    let v2: Value = serde_json::from_str(data).unwrap();
+    assert_eq!(v1, v2)
+}
+
+#[test]
+fn forget_single() {
+    let data = r#"{
+        "memories":[],
+        "messages":[
+            {"content": {"mem": 42 }, "content_type": "object"}
+        ]
+    }"#;
+
+    let mut metadata = HashMap::new();
+    metadata.insert(
+        "var".to_owned(),
+        PrimitiveInt::get_literal(42, Interval::default()),
+    );
+
+    let msg = format_message(
+        Event::new("payload", "", serde_json::json!({})),
+        Context::new(HashMap::new(), metadata, None, None, "forget_single", "flow"),
+        "CSML/basic_test/syntax/forget.csml",
+    );
+
+    let v1: Value = message_to_json_value(msg);
+    let v2: Value = serde_json::from_str(data).unwrap();
+
+    assert_eq!(v1, v2)
+}
+
+#[test]
+fn forget_list() {
+    let data = r#"{
+        "memories":[],
+        "messages":[]
+    }"#;
+    let mut metadata = HashMap::new();
+    metadata.insert(
+        "var".to_owned(),
+        PrimitiveInt::get_literal(42, Interval::default()),
+    );
+
+    let msg = format_message(
+        Event::new("payload", "", serde_json::json!({})),
+        Context::new(HashMap::new(), metadata, None, None, "forget_list", "flow"),
+        "CSML/basic_test/syntax/forget.csml",
+    );
+
+    let v1: Value = message_to_json_value(msg);
+    let v2: Value = serde_json::from_str(data).unwrap();
+    assert_eq!(v1, v2)
+}