diff --git a/.gitignore b/.gitignore
index 2763cd604118ac7bc2655b3c3d4013777b53d9ad..10a0d4bb8636b6947e6c3f929be1d97b6c190d47 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-tests/
+local_tests/
 Cargo.lock
 target/
 utils_macro/
\ No newline at end of file
diff --git a/README.md b/README.md
index d2196441d36b92497c1f2c103bdb8439f2386090..e60aa899f4732f78478a90f591ae8b4fab5015e5 100644
--- a/README.md
+++ b/README.md
@@ -27,3 +27,12 @@ lto = true
 codegen-units = 1
 panic = "abort"
 ```	
+
+### Divers 
+
+```
+clear ; clear ; DEBUG=1 ./install.sh
+clear ; clear ; cargo build --release && DEBUG=1 ./tests/tests.sh "target/release/moustachet" "./tests/*.test"
+clear ; clear ; RUST_BACKTRACE=1 cargo run -- -r --input ./local_tests/source4.test -v myvar="very"
+```
+
diff --git a/install.sh b/install.sh
index 6dd28b29effd9b94ca155c42c95482e63980f1e9..80e0bba59c8d0e3f777ad8a03e31bf30a0c2d2dd 100755
--- a/install.sh
+++ b/install.sh
@@ -25,8 +25,19 @@ else
     echo "See : https://linux.die.net/man/1/upx"
 fi
 
+echo "--> TESTS step"
+./tests/tests.sh "target/$ARCHI/release/moustache" "./tests/*.test"
+if [ $? != 0 ]
+then
+	exit 1
+fi
+
 echo "--> INSTALLATION step"
-# sudo cp "target/$ARCHI/release/moustache" /usr/bin/moustache
-# sudo chmod 775 /usr/bin/moustache
 cp "target/$ARCHI/release/moustache" ~/.local/bin/moustache
 echo "Installation in your local bin directory"
+if [ "$1" = "+sudo" ]
+then
+	sudo cp "target/$ARCHI/release/moustache" /usr/bin/moustache
+	sudo chmod 775 /usr/bin/moustache
+	echo "Installation in your general bin directory"
+fi
diff --git a/src/engine/document.rs b/src/engine/document.rs
index 8fdae58ad1ea003034468e4a1f6793eebde5a524..e64369e7c9c738daa534c3f76a0dfeb8224975f7 100644
--- a/src/engine/document.rs
+++ b/src/engine/document.rs
@@ -2,6 +2,7 @@ use std::fs;
 
 use crate::create_internal_error;
 use crate::engine::environment;
+use crate::engine::Environment;
 use crate::engine::resolver;
 use crate::utils::conf::Configuration;
 use crate::utils::error::InternalError;
@@ -172,7 +173,8 @@ impl<'c> Document<'c> {
     }
     Ok(true)
   }
-  pub fn transform(&mut self) {
+  pub fn transform(&mut self, env: &mut Environment) {
+    env.transform(self);
     let mut destination: String = "".to_string();
     for p in &self.stack {
       match p {
diff --git a/src/engine/environment.rs b/src/engine/environment.rs
index 577de49048eb23c7f9b0284d0d8ba170fd7581f0..63495273e79535706114db046e37708aff1dd7ec 100644
--- a/src/engine/environment.rs
+++ b/src/engine/environment.rs
@@ -2,6 +2,7 @@ use std::collections::HashMap;
 
 use crate::engine::document::Part;
 use crate::utils::conf::Configuration;
+use crate::engine::Document;
 
 #[derive(Debug)]
 pub struct Environment {
@@ -35,4 +36,19 @@ impl Environment {
   pub fn get_block(&self, key: &String) -> Option<&Vec<Part>> {
     self.blocks.get(key)
   }
+  pub fn transform(&mut self, doc: &Document) {
+    for block in self.blocks.values_mut() {
+      let mut destination: String = "".to_string();
+      for value in &mut *block {
+        match value {
+          &mut Part::StaticText(s, e) => destination.push_str(&doc.source[s..e]),
+          Part::GeneratedText(s) => destination.push_str(&s[..]),
+          &mut Part::Statement(s, e) => destination.push_str(&doc.source[s..e]),
+          &mut Part::Expression(s, e) => destination.push_str(&doc.source[s..e]),
+          Part::Comment(_, _) => (),
+        }
+      }
+      *block = vec!(Part::GeneratedText(destination));
+    }
+  }
 }
diff --git a/src/engine/extensions/default.rs b/src/engine/extensions/ext_default.rs
similarity index 96%
rename from src/engine/extensions/default.rs
rename to src/engine/extensions/ext_default.rs
index 9f89425b486feea03859f442364c2939ce19e4fa..3abe8781bbda768c51180c2d9496d401e383fdd0 100644
--- a/src/engine/extensions/default.rs
+++ b/src/engine/extensions/ext_default.rs
@@ -3,7 +3,7 @@ use crate::engine::extensions::Helper;
 use crate::engine::extensions::HelperFunction;
 use crate::engine::extensions::Value;
 
-static MODULE_NAME: &'static str = "default";
+pub static MODULE_NAME: &'static str = "default";
 
 fn recursive_uppercase(value: Value) -> Value {
   match value {
@@ -92,7 +92,6 @@ pub fn help() -> Helper {
   Helper {
     module_name: MODULE_NAME,
     module_description: "Generic functions (UTF-8 compatibility)",
-    module_autor: "Julien Garderon <julien.garderon@gmail.com>",
     functions: vec![
       HelperFunction {
         function_name: "uppercase",
diff --git a/src/engine/extensions/ext_macro.rs b/src/engine/extensions/ext_macro.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b35ad7d683342374d294e36101b3f011d9249b37
--- /dev/null
+++ b/src/engine/extensions/ext_macro.rs
@@ -0,0 +1,97 @@
+use crate::engine::extensions::Context;
+use crate::engine::extensions::Helper;
+use crate::engine::extensions::HelperFunction;
+use crate::engine::extensions::Value;
+
+pub static MODULE_NAME: &'static str = "macro";
+
+fn transform_to_text(v: &Value) -> String {
+  match v {
+    Value::Text(t) => t.to_string(),
+    Value::Symbol(s) => s.to_string(),
+    Value::Number(n) => n.to_string(),
+    Value::Vector(v) => {
+      if v.len() == 0 {
+        "".to_string()
+      } else {
+        v.iter()
+        .map(|s| transform_to_text(s))
+        .reduce(|acc: String, next: String| acc + &next)
+        .unwrap()
+      }
+    }
+    Value::True => "true".to_string(),
+    Value::False | Value::Void => "".to_string(),
+  }
+}
+
+fn execute_convert_to_text(context: &mut Context) -> Option<String> {
+  let v = match context.result.take() {
+    Some(v) => v,
+    None => match context.args.len() {
+      0 => return Some("void pipe and arg".to_string()),
+      1 => match context.args.remove(0) {
+        Value::Text(t) => Value::Text(t.to_string()),
+        Value::Symbol(s) => Value::Text(s.to_string()),
+        v => return Some(format!("invalid token {:?}", v)),
+      } 
+      _ => return Some("too much args".to_string()),
+    }
+  };
+  let r = transform_to_text(&v);
+  context.result = Some(Value::Text(r));
+  None
+}
+
+fn execute_convert_to_symbol(context: &mut Context) -> Option<String> {
+  let v = match context.result.take() {
+    Some(v) => v,
+    None => match context.args.len() {
+      0 => return Some("void pipe and arg".to_string()),
+      1 => match context.args.remove(0) {
+        Value::Text(t) => Value::Text(t.to_string()),
+        Value::Symbol(s) => Value::Text(s.to_string()),
+        v => return Some(format!("invalid token {:?}", v)),
+      } 
+      _ => return Some("too much args".to_string()),
+    }
+  };
+  let r = transform_to_text(&v);
+  if r.is_empty() {
+    return Some("the conversion produced an empty symbol (not allowed)".to_string())
+  }
+  context.result = Some(Value::Symbol(r));
+  None
+}
+
+pub fn execute(context: &mut Context) -> Option<String> {
+  match &context.fct_name[..] {
+    "convert_to_text" => execute_convert_to_text(context),
+    "convert_to_symbol" => execute_convert_to_symbol(context),
+    fct_name => Some(format!(
+      "module {} : unknow function name '{}'",
+      MODULE_NAME, fct_name
+    )),
+  }
+}
+
+pub fn help() -> Helper {
+  Helper {
+    module_name: MODULE_NAME,
+    module_description: "Macro functions",
+    functions: vec![
+      HelperFunction {
+        function_name: "convert_to_text",
+        function_description: "Convert any token or arg to a text",
+        function_can_pipe: true,
+        function_args: "Token or only one arg (priority to pipe)",
+      },
+      HelperFunction {
+        function_name: "convert_to_symbol",
+        function_description: "Convert any token or arg to a non-null symbol",
+        function_can_pipe: true,
+        function_args: "Token or only one arg (priority to pipe)",
+      },
+    ],
+  }
+}
diff --git a/src/engine/extensions/mod.rs b/src/engine/extensions/mod.rs
index d516f5edff340fe45dc92f17eac2c6c21ebab556..3d6c594b5b08e4077b67ba8ee974aff9871cda63 100644
--- a/src/engine/extensions/mod.rs
+++ b/src/engine/extensions/mod.rs
@@ -1,4 +1,5 @@
-pub mod default;
+pub mod ext_default;
+pub mod ext_macro;
 
 use crate::engine::Document;
 use crate::engine::Environment;
@@ -23,7 +24,7 @@ pub struct Context<'a> {
   pub doc_position: usize,
   pub env: &'a mut Environment,
   pub source: &'a str,
-  pub fct_name: String,
+  pub fct_name: &'a str,
   pub args: Vec<Value>,
 }
 
@@ -41,7 +42,7 @@ impl<'a> Context<'a> {
       doc_position: doc_position,
       env: env,
       source: source,
-      fct_name: "".to_string(),
+      fct_name: &source[0..0],
       args: vec![],
     }
   }
@@ -51,7 +52,6 @@ impl<'a> Context<'a> {
 pub struct Helper {
   module_name: &'static str,
   module_description: &'static str,
-  module_autor: &'static str,
   functions: Vec<HelperFunction>,
 }
 
@@ -59,9 +59,9 @@ impl Helper {
   pub fn display(&self) {
     println!(
       "
-  ♦ Extension '{}' - by {} 
+  ♦ Extension '{}' 
     {}",
-      self.module_name, self.module_autor, self.module_description
+      self.module_name, self.module_description
     );
     for f in self.functions.iter() {
       f.display();
@@ -92,3 +92,19 @@ impl HelperFunction {
     );
   }
 }
+
+pub fn execute<'a>(module: &str, context: &mut Context) -> Option<String> {
+  match module {
+    m if m == ext_default::MODULE_NAME => ext_default::execute(context),
+    m if m == ext_macro::MODULE_NAME => ext_macro::execute(context),
+    m => Some(format!(
+      "Extension '{}' not found (--help-extensions argument may assist you)",
+      m
+    )),
+  }
+}
+
+pub fn help() {
+  ext_default::help().display();
+  ext_macro::help().display();
+}
diff --git a/src/engine/parser.rs b/src/engine/parser.rs
index 137e175996d537d3a2e5217d4f5ec18eef61aec5..677b8b6acd597d8e302eca6fffb0d4b67ab1707d 100644
--- a/src/engine/parser.rs
+++ b/src/engine/parser.rs
@@ -100,10 +100,6 @@ pub fn parse<'a>(source: &'a str) -> Result<Vec<Token>, InternalError> {
   let mut portion_start: usize = 0;
   let mut is_escaping: bool = false;
   for (i, c) in source.char_indices() {
-    // println!(
-    //   "(boucle) i = {:?} ; c = {:?} ; is_text = {:?} ; portion_start = {:?} ; is_escaping = {:?}",
-    //   i, c, is_text, portion_start, is_escaping
-    // );
     if is_text == true && is_escaping == true {
       is_escaping = false;
       continue;
@@ -112,7 +108,7 @@ pub fn parse<'a>(source: &'a str) -> Result<Vec<Token>, InternalError> {
     }
     match c {
       ' ' | '\t' | '\n' | '\r' if is_text == false => {
-        if i > 0 && portion_start < i - 1 {
+        if i > 0 && portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         let space_type = match c {
@@ -132,70 +128,70 @@ pub fn parse<'a>(source: &'a str) -> Result<Vec<Token>, InternalError> {
         portion_start = i + 1;
       }
       '(' if is_text == false => {
-        if portion_start < i - 1 {
+        if portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         stack.push(Token::ParenthesisOpening);
         portion_start = i + 1;
       }
       ')' if is_text == false => {
-        if portion_start < i - 1 {
+        if portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         stack.push(Token::ParenthesisEnding);
         portion_start = i + 1;
       }
       '+' if is_text == false => {
-        if portion_start < i - 1 {
+        if portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         stack.push(Token::Plus);
         portion_start = i + 1;
       }
       '-' if is_text == false => {
-        if portion_start < i - 1 {
+        if portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         stack.push(Token::Minus);
         portion_start = i + 1;
       }
       '=' if is_text == false => {
-        if portion_start < i - 1 {
+        if portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         stack.push(Token::Equal);
         portion_start = i + 1;
       }
       '/' if is_text == false => {
-        if portion_start < i - 1 {
+        if portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         stack.push(Token::Divide);
         portion_start = i + 1;
       }
       '*' if is_text == false => {
-        if portion_start < i - 1 {
+        if portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         stack.push(Token::Multiply);
         portion_start = i + 1;
       }
       '|' if is_text == false => {
-        if portion_start < i - 1 {
+        if portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         stack.push(Token::Pipe);
         portion_start = i + 1;
       }
       '&' if is_text == false => {
-        if portion_start < i - 1 {
+        if portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         stack.push(Token::Ampersand);
         portion_start = i + 1;
       }
       '!' if is_text == false => {
-        if portion_start < i - 1 {
+        if portion_start < i {
           stack.push(Token::Symbol(portion_start, i));
         }
         stack.push(Token::Exclamation);
diff --git a/src/engine/resolver/statement/mod.rs b/src/engine/resolver/statement/mod.rs
index ec4d6384e8a39e004c3d7b1ff892f852b1c478a4..06122ac89214c5ebe13cd73d796ec4d89343b4ce 100644
--- a/src/engine/resolver/statement/mod.rs
+++ b/src/engine/resolver/statement/mod.rs
@@ -109,7 +109,8 @@ pub fn resolve_statement<'a>(
             return Err(add_step_internal_error!(
               err,
               "error in 'set' statement",
-              format!("source = '\x1b[3m{}\x1b[0m'", source.trim())
+              format!("source = '\x1b[3m{}\x1b[0m'", source.trim()),
+              "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'"
             ))
           }
         },
@@ -118,8 +119,7 @@ pub fn resolve_statement<'a>(
           Err(mut err) => {
             return Err(add_step_internal_error!(
               err,
-              "error in 'execute' statement",
-              format!("source = '\x1b[3m{}\x1b[0m'", source.trim())
+              "error in 'execute' statement"
             ))
           }
         },
diff --git a/src/engine/resolver/statement/unit_execute.rs b/src/engine/resolver/statement/unit_execute.rs
index bd66f19023b746965282fa70ec60c2c53c877ed7..a53f807d491ee1cca8b88cded18460a59891a17b 100644
--- a/src/engine/resolver/statement/unit_execute.rs
+++ b/src/engine/resolver/statement/unit_execute.rs
@@ -3,7 +3,7 @@ use std::iter::Peekable;
 
 use crate::add_step_internal_error;
 use crate::create_internal_error;
-use crate::engine::extensions::default;
+use crate::engine::extensions;
 use crate::engine::extensions::Context;
 use crate::engine::extensions::Value;
 use crate::engine::resolver::statement::Token;
@@ -15,7 +15,7 @@ fn resolve_fct<'a>(
   context: &mut Context,
   iter_tokens: &mut Peekable<Iter<'_, Token>>,
 ) -> Option<String> {
-  let fct: String;
+  let fct: &str;
   loop {
     let token = match iter_tokens.next() {
       Some(t) => t,
@@ -24,7 +24,7 @@ fn resolve_fct<'a>(
     match token {
       Token::Space(_) => (),
       &Token::Symbol(s, e) => {
-        fct = context.source[s..e].to_string();
+        fct = &context.source[s..e];
         break;
       }
       t => {
@@ -83,15 +83,12 @@ fn resolve_fct<'a>(
       fct
     ));
   }
-  context.fct_name = f.get(1).unwrap().to_string();
+  context.fct_name = f.get(1).unwrap();
   context.args = args;
-  match *f.get(0).unwrap() {
-    "default" => default::execute(context),
-    n => Some(format!(
-      "Extension '{}' not found (--help-extensions argument may assist you)",
-      n
-    )),
-  }
+  extensions::execute(
+    f.get(0).unwrap(),
+    context,
+  )
 }
 
 fn cast(env: &mut Environment, results: Option<Value>) -> Result<String, String> {
@@ -143,7 +140,6 @@ pub fn resolve_unit<'a>(
     let token = match iter_tokens.next() {
       Some(t) => t,
       None => {
-        println!("---------- > none");
         return Err(create_internal_error!(
           "The statement can't be empty".to_string()
         ));
diff --git a/src/engine/resolver/statement/unit_set.rs b/src/engine/resolver/statement/unit_set.rs
index be07cf305fb681152e319cba8db480aa4fdeca52..df510bf9b94959b3adee9fe59c2e2513a74434f6 100644
--- a/src/engine/resolver/statement/unit_set.rs
+++ b/src/engine/resolver/statement/unit_set.rs
@@ -16,10 +16,7 @@ pub fn resolve_unit<'a>(
     let token = match iter_tokens.next() {
       Some(t) => t,
       None => {
-        return Err(create_internal_error!(
-          "Statement can't be empty",
-          "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'"
-        ))
+        return Err(create_internal_error!("Statement can't be empty"))
       }
     };
     match token {
@@ -30,12 +27,7 @@ pub fn resolve_unit<'a>(
       }
       t => {
         return Err(create_internal_error!(
-          format!("Found '{}' in first part (must be Token::Symbol)", t),
-          "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'",
-          format!(
-            "found statement (here with trim !) = '\x1b[3m{}\x1b[0m'",
-            source.trim()
-          )
+          format!("Found '{}' in first part (must be Token::Symbol)", t)
         ));
       }
     }
@@ -45,12 +37,7 @@ pub fn resolve_unit<'a>(
       Some(t) => t,
       None => {
         return Err(create_internal_error!(
-          "Statement must be complete (token 'equal' not found, premature end)",
-          "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'",
-          format!(
-            "found statement (here with trim !) = '\x1b[3m{}\x1b[0m'",
-            source.trim()
-          )
+          "Statement must be complete (token 'equal' not found, premature end)"
         ))
       }
     };
@@ -60,12 +47,7 @@ pub fn resolve_unit<'a>(
       t => {
         return Err(create_internal_error!(
           "Incorrect token after the first part (must be Token::Equal)",
-          "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'",
-          format!(
-            "found statement (here with trim !) = '\x1b[3m{}\x1b[0m' (incorrect token : {})",
-            source.trim(),
-            t
-          )
+          format!("token found: {})", t)
         ))
       }
     }
@@ -73,6 +55,7 @@ pub fn resolve_unit<'a>(
   let mut value: Vec<String> = vec![];
   let mut begining: bool = true;
   let mut operator: bool = false;
+  let mut if_part: bool = false; 
   loop {
     let token =
       match iter_tokens.next() {
@@ -80,14 +63,11 @@ pub fn resolve_unit<'a>(
         None => {
           if begining {
             return Err(create_internal_error!(
-            "The second part cannot be empty",
-            "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'"
+            "The second part cannot be empty"
           ));
           } else if operator == false {
             return Err(create_internal_error!(
-            "Invalid ending : an operator without symbol or text after",
-            "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'",
-            format!("found statement (here with trim !) = '\x1b[3m{}\x1b[0m'", source.trim())
+            "Invalid ending : an operator without symbol or text after"
           ));
           } else {
             break;
@@ -113,10 +93,8 @@ pub fn resolve_unit<'a>(
         } else {
           return Err(create_internal_error!(
             "Operator missing between symbol or text",
-            "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'",
             format!(
-              "found statement (here with trim !) = '\x1b[3m{}\x1b[0m' (error on symbol : '{}' - position {} ~> {})",
-              source.trim(),
+              "error on symbol : '{}' - position {} ~> {}",
               key,
               s,
               e
@@ -131,10 +109,8 @@ pub fn resolve_unit<'a>(
         } else {
           return Err(create_internal_error!(
             "Operator missing between symbol or text",
-            "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'",
             format!(
-              "found statement (here with trim !) = '\x1b[3m{}\x1b[0m' (error on text : '{}' - position {} ~> {})",
-              source.trim(),
+              "error on text : '{}' - position {} ~> {}",
               source[s..e].to_string(),
               s,
               e
@@ -142,28 +118,112 @@ pub fn resolve_unit<'a>(
           ));
         }
       }
-      &Token::Plus => {
+      Token::Plus => {
         if operator == true {
           operator = false;
         } else {
           return Err(create_internal_error!(
-            "Symbol or text missing before operator",
-            "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'"
+            "Symbol or text missing before operator"
+          ));
+        }
+      }
+      Token::Exclamation => {
+        if operator == true {
+          if_part = true;
+          break;
+        } else {
+          return Err(create_internal_error!(
+            "an 'if' part cannot directly follow an operator"
           ));
         }
       }
       t => {
         return Err(create_internal_error!(
-          format!("Found '{}' in first part (must be Token::Symbol)", t),
-          "must be = '\x1b[3mset [symbol] = [text or symbol (+ text or symbol (+ ...))]\x1b[0m'",
-          format!(
-            "found statement (here with trim !) = '\x1b[3m{}\x1b[0m'",
-            source.trim()
-          )
+          format!("Found '{}' in second part (must be Token::Symbol)", t)
         ))
       }
     }
   }
-  env.set(key, value.into_iter().collect::<String>());
+  let mut empty: bool = true;
+  if if_part {
+    loop {
+      let token = match iter_tokens.next() {
+        Some(t) => t,
+        None => {
+          return Err(create_internal_error!("'if' part can't be empty (first token)"))
+        }
+      };
+      match token {
+        Token::Space(_) => (),
+        &Token::Symbol(s, e) => {
+          let s: &str = &source[s..e];
+          if s == "if" {
+            break;
+          } else {
+            return Err(create_internal_error!(
+              format!("Found '{}' in 'if' part of statement (must be Token::Symbol['if'])", s)
+            ));
+          }
+        }
+        t => {
+          return Err(create_internal_error!(
+            format!("Found '{}' in 'if' part of statement (must be Token::Symbol['if'])", t)
+          ));
+        }
+      }
+    }
+    loop {
+      let token = match iter_tokens.next() {
+        Some(t) => t,
+        None => {
+          return Err(create_internal_error!("'if' part can't be empty (second token)"))
+        }
+      };
+      match token {
+        Token::Space(_) => (),
+        &Token::Symbol(s, e) => {
+          match &source[s..e] {
+            "empty" => break,
+            "setted" => {
+              empty = false;
+              break;
+            },
+            s => return Err(create_internal_error!(
+              format!("Found '{}' in 'if' part of statement (must be Token::Symbol['empty' or 'setted'])", s)
+            ))
+          }
+        }
+        t => {
+          return Err(create_internal_error!(
+            format!("Found '{}' in 'if' part of statement (must be Token::Symbol['if'])", t)
+          ));
+        }
+      }
+    }
+  }
+  let setting: bool = if if_part {
+    let exists = match env.get(&key) {
+      Some(_) => true,
+      None => false,
+    };
+    if empty {
+      if exists {
+        false
+      } else {
+        true
+      }
+    } else {
+      if exists {
+        true
+      } else {
+        false
+      }      
+    }
+  } else {
+    true
+  };
+  if setting {
+    env.set(key, value.into_iter().collect::<String>());
+  } 
   Ok(())
 }
diff --git a/src/main.rs b/src/main.rs
index a0fd05bea1feed455f7f2d717dcbc2c38015eb0a..097de536b2d106c1020d7723bc1a49b0926d2082 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -108,7 +108,7 @@ fn main() {
       Ok(changed) => {
         if changed {
           let _ = display_debug_block!(conf, "Resolve parts", "Document is changed");
-          doc.transform();
+          doc.transform(&mut env);
         } else {
           if reentrance > 0 {
             let _ = display_debug_block!(conf, "Resolve parts", "Document is not changed");
@@ -142,7 +142,7 @@ fn main() {
         let _ = display_debug_block!(conf, "Write output", "Path = {:?}", path);
       }
     },
-    None => println!("{}", doc.source),
+    None => print!("{}", doc.source),
   }
 
   display_debug_title!(conf, "End of program, no errors");
diff --git a/src/utils/args.rs b/src/utils/args.rs
index f6cac7f4a24cc265d4c4340bd91ec144287c4aae..1b393112f955e32fdd66ac3bdbf8f0c991766f26 100644
--- a/src/utils/args.rs
+++ b/src/utils/args.rs
@@ -1,7 +1,7 @@
 use std::env;
 
 // use utils_macro::modifier_item;
-use crate::engine::extensions::default;
+use crate::engine::extensions;
 use crate::utils::APP_AUTHOR;
 use crate::utils::APP_DATE;
 use crate::utils::APP_NAME;
@@ -104,5 +104,5 @@ Extensions documentation
 ------------------------
 "
   );
-  default::help().display();
+  extensions::help();
 }
diff --git a/src/utils/mod.rs b/src/utils/mod.rs
index 81fc84df3ad7dc1bee61951fc98917d77f1077ea..e0f6c2ffe32f6afb562482dfe4be78e5cfaffa28 100644
--- a/src/utils/mod.rs
+++ b/src/utils/mod.rs
@@ -3,7 +3,7 @@ pub mod conf;
 pub mod error;
 
 pub static APP_NAME: &'static str = "Moustache";
-pub static APP_VERSION: &'static str = "v1.0.0";
+pub static APP_VERSION: &'static str = "v1.0.0-release-candidate";
 pub static APP_DATE: &'static str = "april 2024";
 pub static APP_AUTHOR: &'static str = "Julien Garderon <julien.garderon@gmail.com>";
 
diff --git a/tests/t1.test b/tests/t1.test
new file mode 100644
index 0000000000000000000000000000000000000000..a6fd94f0807435b69ed2bfa9259a2148533104a2
--- /dev/null
+++ b/tests/t1.test
@@ -0,0 +1,29 @@
+---separator
+$moustache -v myvar="all fine" -v my_another_var="!" 
+---separator
+{{ myvar }}
+{{myvar}}
+{{ myvar}}
+{{myvar }}
+{{ myvar + my_another_var }}
+{{ myvar+my_another_var }}
+{{ myvar+ my_another_var }}
+{{ myvar +my_another_var }}
+{{myvar+my_another_var}}
+{{ myvar+my_another_var}}
+{{myvar+my_another_var }}
+{{ myvar + my_another_var + my_another_var }}
+---separator
+all fine
+all fine
+all fine
+all fine
+all fine!
+all fine!
+all fine!
+all fine!
+all fine!
+all fine!
+all fine!
+all fine!!
+---separator
diff --git a/tests/t2.test b/tests/t2.test
new file mode 100644
index 0000000000000000000000000000000000000000..1b65a79964183251395c8198081337711a6df8e9
--- /dev/null
+++ b/tests/t2.test
@@ -0,0 +1,13 @@
+---separator
+$moustache -v myvar="all fine"
+---separator
+{% set myvar = "all fine but another" %}{{ myvar }}
+{% set a = "0" ! if empty %}{{ a }}
+{% set a = "1" %}{{ a }}
+{% set a = "2" ! if setted %}{{ a }}
+---separator
+all fine but another
+0
+1
+2
+---separator
diff --git a/tests/t3.test b/tests/t3.test
new file mode 100644
index 0000000000000000000000000000000000000000..b77fc588e8cda25fb4d3bdd4429811e6bfbd6cfe
--- /dev/null
+++ b/tests/t3.test
@@ -0,0 +1,22 @@
+---separator
+$moustache 2>&1 || exit 0
+---separator
+{{ undefined_var }}
+---separator
+
+-- ERROR FOUND
+
+[0] >> Error during resolving
+       v1.0.0-release-candidate/src/main.rs#120)
+
+[1] >> Error in expression
+       v1.0.0-release-candidate/src/engine/resolver/mod.rs#33)
+       real position of expression in document = 0 -> 19
+       target expression (here with trim !) = 'undefined_var'
+       must be in the following form = '{{ text or symbol (+ text or symbol (+ ...)) }}'
+
+[2] >> Undefined variable 'undefined_var' in environment
+       v1.0.0-release-candidate/src/engine/resolver/expression/mod.rs#68)
+
+--
+---separator
diff --git a/tests/t4.test b/tests/t4.test
new file mode 100644
index 0000000000000000000000000000000000000000..b6352310d7fd270e0352bdbedecd55fba2c77f90
--- /dev/null
+++ b/tests/t4.test
@@ -0,0 +1,19 @@
+---separator
+$moustache -r -v myvar=very
+---separator
+{% block "myblock" %}all is {{ myvar }} fine!{% endblock %}
+{% call "myblock" %}
+{% call "myblock" %}
+{% set name_of_block = "b2" %}
+{% block name_of_block %}no{% endblock %}
+{% call name_of_block %}
+{% block "b2" %}nothing is {{ myvar }} better.{% endblock %}
+{% if myvar == "very" %}
+{% call name_of_block %}
+{% endif %}
+---separator
+all is very fine!
+all is very fine!
+no
+nothing is very better.
+---separator
diff --git a/tests/tests.sh b/tests/tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..f2c1719911f4696f57550cb1b5e0e0ac7e622af7
--- /dev/null
+++ b/tests/tests.sh
@@ -0,0 +1,121 @@
+#!/usr/bin/env bash
+
+function display_title() {
+  [[ $QUIET == "" ]] && echo "$1"
+}
+
+function display_result() {
+  [[ $QUIET == "" ]] && echo "  --> $1"
+}
+
+function debug() {
+  [ $DEBUG ] && echo "  >> $1"
+}
+
+function retrieve_part() {
+  TESTFILE_PATH=$1
+  TESTFILE_SEPARATOR=$2
+  ITER=0
+  FOUND=
+  debug "try retrieve $3..."
+  while read -r line
+  do 
+    debug "read line n°$ITER : $line"
+    if [ "$line" == "$TESTFILE_SEPARATOR" ] 
+    then
+      FOUND=$ITER
+      break
+    fi
+    ITER=$(expr $ITER + 1)
+  done < <(tail -n "+$GLOBAL_ITER" "$TESTFILE_PATH")
+  `echo declare -g "$3"_START=$GLOBAL_ITER`
+  `echo declare -g "$3"_END=$ITER`
+  GLOBAL_ITER=$(expr $GLOBAL_ITER + $ITER + 1)
+  if [ "$FOUND" ]
+  then
+    debug "$3 found ($FOUND line[s]) ! next step..."
+  else
+    debug "$3 not found ! incorrect test file"
+    exit 1
+  fi
+}
+
+function execute_test () {
+  # --- step 0
+  EXEC_PATH=$1
+  TESTFILE_PATH=$2
+  debug "process file : '$TESTFILE_PATH'"
+  TESTFILE_SEPARATOR=`head -n 1 "$TESTFILE_PATH"`
+  debug "found separator : '$TESTFILE_SEPARATOR'"
+  GLOBAL_ITER=2
+  # --- step 1
+  retrieve_part $TESTFILE_PATH $TESTFILE_SEPARATOR "CONF"
+  TEST_COMMAND=`tail -n "+$CONF_START" "$TESTFILE_PATH" | head -n "$CONF_END" | tr -d "\n"`
+  # --- step 2
+  retrieve_part $TESTFILE_PATH $TESTFILE_SEPARATOR "SOURCE_IN"
+  # --- step 3
+  retrieve_part $TESTFILE_PATH $TESTFILE_SEPARATOR "SOURCE_OUT"
+  # --- step 4
+  display_result "process command : '$TEST_COMMAND'"
+  RESULT="`tail -n "+$SOURCE_IN_START" "$TESTFILE_PATH" | head -n "$SOURCE_IN_END" | sh -c "moustache='$EXEC_PATH'; $TEST_COMMAND 2>&1"`" 
+  EXPECTED_RESULT="`tail -n "+$SOURCE_OUT_START" "$TESTFILE_PATH" | head -n "$SOURCE_OUT_END"`" 
+  RESULT=$(echo $RESULT | LC_ALL=C.UTF8 sed -E "s/\x1B\[[\x30-\x3F]*[\x20-\x20F]*[\x40-\x7E]//g")
+  EXPECTED_RESULT=$(echo $EXPECTED_RESULT | LC_ALL=C.UTF8 sed -E "s/\x1B\[[\x30-\x3F]*[\x20-\x20F]*[\x40-\x7E]//g")
+  debug "return : !EOF"
+  debug "$RESULT"
+  debug "EOF!"
+  debug "expected : !EOF"
+  debug "$EXPECTED_RESULT"
+  debug "EOF!"
+  echo "return code $RETURNCODE"
+  if [ "$EXPECTED_RESULT" = "$RESULT" ]
+  then
+    debug "expected result found"
+    return 0
+  else
+    debug "expected result not found"
+    return 1
+  fi
+}
+
+function execute_tests() {
+  if [[ $2 == "" ]]
+  then 
+    display_title "tests path not defined"
+    exit 0
+  fi
+  for path in `ls $2`
+  do
+    display_title "Found '$path' test file"
+    execute_test "$1" "$path"
+    RETURNCODE="$?"
+    if [ "$RETURNCODE" == 0 ]
+    then
+      display_result "test passed !"
+    else
+      display_result "test failed !"
+      KO=$(expr $KO + 1)
+    fi
+    SUM=$(expr $SUM + 1)
+  done
+}
+
+if [ "$DEBUG" == "0" ]
+then
+  DEBUG=
+fi
+
+display_title "EXECUTE PATH = $1"
+
+SUM=0
+KO=0
+execute_tests "$1" "$2"
+if [ $KO == 0 ]
+then 
+  display_title "All tests passed ($SUM test[s])"
+  exit 0
+else
+  display_title "$KO test[s] failed (/ $SUM test[s])"
+  exit 1
+fi
+