diff --git a/src/language/ASTBuilder.cpp b/src/language/ASTBuilder.cpp
index 1a52ced9dd48d8781eb5c7360f1511c09bf92737..ab30b2aaa2ae3b271d60b29035ea94428989eac0 100644
--- a/src/language/ASTBuilder.cpp
+++ b/src/language/ASTBuilder.cpp
@@ -260,8 +260,12 @@ using selector = parse_tree::selector<Rule,
 
 }   // namespace language
 
+template <typename InputT>
 std::unique_ptr<language::Node>
-buildAST(read_input<>& input)
+buildAST(InputT& input)
 {
   return parse_tree::parse<language::grammar, language::Node, language::selector, nothing, language::errors>(input);
 }
+
+template std::unique_ptr<language::Node> buildAST(read_input<>& input);
+template std::unique_ptr<language::Node> buildAST(istream_input<>& input);
diff --git a/src/language/ASTBuilder.hpp b/src/language/ASTBuilder.hpp
index d6ccc2c8b3c974159143bddc84b5349af469eb3d..19adacf161b2a330fb89d4145a82816fec698630 100644
--- a/src/language/ASTBuilder.hpp
+++ b/src/language/ASTBuilder.hpp
@@ -5,6 +5,10 @@
 
 #include <ASTNode.hpp>
 
-std::unique_ptr<language::Node> buildAST(read_input<>& input);
+template <typename InputT>
+std::unique_ptr<language::Node> buildAST(InputT& input);
+
+template <typename InputT>
+std::unique_ptr<language::Node> buildAST(InputT& input);
 
 #endif   // AST_BUILDER_HPP
diff --git a/src/language/ASTPrinter.cpp b/src/language/ASTPrinter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..572a97a11a9e48c59599599db941cf43748f72bd
--- /dev/null
+++ b/src/language/ASTPrinter.cpp
@@ -0,0 +1,89 @@
+#include <ASTPrinter.hpp>
+#include <EscapedString.hpp>
+
+namespace language
+{
+void
+ASTPrinter::_print(std::ostream& os, const Node& node) const
+{
+  os << '(' << rang::fgB::yellow;
+  if (node.is_root()) {
+    os << "root";
+  } else {
+    os << node.name();
+  }
+  os << rang::fg::reset << ':';
+  os << dataTypeName(node.m_data_type) << ':';
+
+  os << rang::fgB::cyan;
+  std::visit(
+    [&](const auto& value) {
+      using T = std::decay_t<decltype(value)>;
+      if constexpr (std::is_same_v<T, std::monostate>) {
+        os << "--";
+      } else if constexpr (std::is_same_v<T, std::string>) {
+        os << '\"' << escapeString(value) << '\"';
+      } else {
+        os << value;
+      }
+    },
+    node.m_value);
+  os << rang::fg::reset << ")\n";
+
+  if (not node.children.empty()) {
+    _print(os, node.children);
+  }
+}
+
+template <typename NodeVector>
+void
+ASTPrinter::_print(std::ostream& os, const NodeVector& node_list) const
+{
+  for (size_t i_child = 0; i_child < node_list.size(); ++i_child) {
+    if (i_child != node_list.size() - 1) {
+      os << rang::fgB::green << prefix << T_junction << rang::fg::reset;
+    } else {
+      os << rang::fgB::green << prefix << L_junction << rang::fg::reset;
+    }
+    auto& child = *(node_list[i_child]);
+    if (not child.children.empty()) {
+      last_prefix_size.push_back(prefix.size());
+      if (i_child != node_list.size() - 1) {
+        prefix += pipe_space;
+      } else {
+        prefix += space_space;
+      }
+
+      _print(os, *(node_list[i_child]));
+
+      prefix.resize(last_prefix_size[last_prefix_size.size() - 1]);
+      last_prefix_size.pop_back();
+    } else {
+      _print(os, *(node_list[i_child]));
+    }
+  }
+}
+
+std::ostream&
+operator<<(std::ostream& os, const ASTPrinter& ast_printer)
+{
+  ast_printer._print(os, ast_printer.m_node);
+  return os;
+}
+
+ASTPrinter::ASTPrinter(const language::Node& node, Format format) : m_node{node}
+{
+  if (format == Format::pretty) {
+    T_junction  = " \u251c\u2500\u2500";
+    L_junction  = " \u2514\u2500\u2500";
+    pipe_space  = " \u2502  ";
+    space_space = "    ";
+  } else {
+    Assert(format == Format::raw);
+    T_junction  = " +-";
+    L_junction  = " `-";
+    pipe_space  = " |  ";
+    space_space = "    ";
+  }
+}
+}   // namespace language
diff --git a/src/language/ASTPrinter.hpp b/src/language/ASTPrinter.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a4715fba240bcb2e0ee7f8da6a7272cfacf10788
--- /dev/null
+++ b/src/language/ASTPrinter.hpp
@@ -0,0 +1,46 @@
+#ifndef AST_PRINTER_HPP
+#define AST_PRINTER_HPP
+
+#include <ASTNode.hpp>
+
+namespace language
+{
+class ASTPrinter
+{
+ public:
+  enum class Format
+  {
+    raw,
+    pretty
+  };
+
+ private:
+  const Node& m_node;
+
+  mutable std::string prefix;
+  mutable std::vector<int> last_prefix_size;
+
+  std::string T_junction;
+  std::string L_junction;
+
+  std::string pipe_space;
+  std::string space_space;
+
+  void _print(std::ostream& os, const Node& node) const;
+
+  template <typename NodeVector>
+  void _print(std::ostream& os, const NodeVector& node_list) const;
+
+ public:
+  friend std::ostream& operator<<(std::ostream& os, const ASTPrinter& ast_printer);
+
+  ASTPrinter(const Node& node, Format format = Format::pretty);
+
+  ASTPrinter(const ASTPrinter&) = delete;
+
+  ASTPrinter(ASTPrinter&&) = delete;
+
+  ~ASTPrinter() = default;
+};
+}   // namespace language
+#endif   // AST_PRINTER_HPP
diff --git a/src/language/CMakeLists.txt b/src/language/CMakeLists.txt
index c84943ca41a9ccc1757e92ad3f7e0ff1e28e5903..8cf155b464da49f94cfc7e32cb9a2cdb0168a3b6 100644
--- a/src/language/CMakeLists.txt
+++ b/src/language/CMakeLists.txt
@@ -12,6 +12,7 @@ add_library(
   ASTNodeExpressionBuilder.cpp
   ASTNodeIncDecExpressionBuilder.cpp
   ASTNodeUnaryOperatorExpressionBuilder.cpp
+  ASTPrinter.cpp
   PugsParser.cpp)
 
 #include_directories(${PUGS_SOURCE_DIR}/utils)
diff --git a/src/language/PugsParser.cpp b/src/language/PugsParser.cpp
index 08f9c48613731f25795401c85dcca2a942eb89d5..2edd84b3b6b2f9497a5a4e15c0509c5995c225fa 100644
--- a/src/language/PugsParser.cpp
+++ b/src/language/PugsParser.cpp
@@ -23,6 +23,8 @@
 
 #include <ASTNodeExpressionBuilder.hpp>
 
+#include <ASTPrinter.hpp>
+
 namespace language
 {
 namespace internal
@@ -443,77 +445,6 @@ simplify_declarations(Node& n)
   internal::simplify_declarations(n);
 }
 
-void print(const Node& n);
-
-std::string prefix;
-std::vector<int> last_prefix_size;
-const std::string T_junction(" \u251c\u2500\u2500");
-const std::string L_junction(" \u2514\u2500\u2500");
-
-const std::string pipe_space(" \u2502  ");
-const std::string space_space("    ");
-
-template <typename NodeVector>
-void
-print(const NodeVector& node_list)
-{
-  for (size_t i_child = 0; i_child < node_list.size(); ++i_child) {
-    if (i_child != node_list.size() - 1) {
-      std::cout << rang::fgB::green << prefix << T_junction << rang::fg::reset;
-    } else {
-      std::cout << rang::fgB::green << prefix << L_junction << rang::fg::reset;
-    }
-    auto& child = *(node_list[i_child]);
-    if (not child.children.empty()) {
-      last_prefix_size.push_back(prefix.size());
-      if (i_child != node_list.size() - 1) {
-        prefix += pipe_space;
-      } else {
-        prefix += space_space;
-      }
-
-      print(*(node_list[i_child]));
-
-      prefix.resize(last_prefix_size[last_prefix_size.size() - 1]);
-      last_prefix_size.pop_back();
-    } else {
-      print(*(node_list[i_child]));
-    }
-  }
-}
-
-void
-print(const Node& n)
-{
-  std::cout << '(' << rang::fgB::yellow;
-  if (n.is_root()) {
-    std::cout << "root";
-  } else {
-    std::cout << n.name();
-  }
-  std::cout << rang::fg::reset << ':';
-  std::cout << dataTypeName(n.m_data_type) << ':';
-
-  std::cout << rang::fgB::cyan;
-  std::visit(
-    [](const auto& value) {
-      using T = std::decay_t<decltype(value)>;
-      if constexpr (std::is_same_v<T, std::monostate>) {
-        std::cout << "--";
-      } else if constexpr (std::is_same_v<T, std::string>) {
-        std::cout << '\"' << escapeString(value) << '\"';
-      } else {
-        std::cout << value;
-      }
-    },
-    n.m_value);
-  std::cout << rang::fg::reset << ")\n";
-
-  if (not n.children.empty()) {
-    print(n.children);
-  }
-}
-
 }   // namespace language
 
 void
@@ -553,7 +484,7 @@ parser(const std::string& filename)
 
     language::build_node_type(*root_node);
 
-    language::print(*root_node);
+    std::cout << language::ASTPrinter{*root_node, language::ASTPrinter::Format::raw} << '\n';
 
     language::ExecUntilBreakOrContinue exec_all;
     root_node->execute(exec_all);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 4eef6c18771aa6768877e9b24a3afe8e91296911..0272ba4f1a526b003eb96fc1bd94463001f5f8b5 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -4,6 +4,7 @@ set(EXECUTABLE_OUTPUT_PATH ${PUGS_BINARY_DIR})
 
 add_executable (unit_tests
   test_main.cpp
+  test_ASTBuilder.cpp
   test_Array.cpp
   test_ArrayUtils.cpp
   test_ItemType.cpp
@@ -22,6 +23,7 @@ target_include_directories(Catch2 INTERFACE ${CATCH_INCLUDE_DIR})
 
 target_link_libraries (unit_tests
   PugsUtils
+  PugsLanguage
   kokkos
   Catch2
   )
diff --git a/tests/test_ASTBuilder.cpp b/tests/test_ASTBuilder.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..11c2afc9497c2362437c28c9f9fbb9fb4e3616e5
--- /dev/null
+++ b/tests/test_ASTBuilder.cpp
@@ -0,0 +1,49 @@
+#include <catch.hpp>
+
+#include <ASTBuilder.hpp>
+#include <ASTPrinter.hpp>
+#include <sstream>
+
+TEST_CASE("ASTBuilder", "[language]")
+{
+  using namespace language;
+
+  SECTION("checking AST simplifications"){}
+  {
+    std::stringstream ss{R"(
+Z a = -3;
+Z b = - - + - 2;
+Z d;
+{
+ Z c=3;
+ d = (0 -  -c)/3;
+}
+Z e = 0 + +3;
+Z f = 0 + -3;
+
+B g = not not true;
+R h = 0;
+R i = h++;
+i--;
+)"};
+
+    istream_input input{ss, ss.str().size(), "test.pgs"};
+    auto ast = buildAST(input);
+    std::stringstream ast_output;
+    ast_output << ASTPrinter{*ast, ASTPrinter::Format::raw};
+    REQUIRE(true);
+  }
+
+  REQUIRE(true);
+
+  SECTION("checking for copies") {}
+
+  SECTION("checking for fill") {}
+
+  SECTION("checking for affectations (shallow copy)") {}
+
+  SECTION("checking for bounds violation")
+  {
+    //    REQUIRE_THROWS_AS(a[10], AssertError);
+  }
+}