diff --git a/doc/userdoc.org b/doc/userdoc.org
index 58d78e37b7d36b594bc756fa06ffa67c06d24cf5..5ff07d98b6d7294dac115a74b6b719883c5b7b0f 100644
--- a/doc/userdoc.org
+++ b/doc/userdoc.org
@@ -180,9 +180,9 @@ In ~pugs~, all these "parameters" are set through a
 DSL[fn:DSL-def]. Thus, when ~pugs~ is launched, it actually executes a
 provided script. A ~C++~ function is associated to each instruction of
 the script. The ~C++~ components of ~pugs~ are completely unaware one of
-the others. ~pugs~ interpreter is responsible of data flow between the
-components: it manages the data transfer between those ~C++~ components
-and ensures that the workflow is properly defined.
+the others. ~pugs~ interpreter is responsible for the data flow between
+the components: it manages the data transfer between those ~C++~
+components and ensures that the workflow is properly defined.
 
 **** Why?
 
@@ -224,7 +224,7 @@ methods or their settings.
 Using directly the general purpose language (~C~, ~C++~, ~Fortran~,...) used
 to write the code can be tempting. It has the advantage that no
 particular treatment is necessary to build a parser (to read data
-files or a script), but it presents several drawbacks.
+files or scripts), but it presents several drawbacks.
 
 - The first one is probably that it allows too much freedom. While
   defining the model and numerical options, the user has generally
@@ -239,7 +239,7 @@ files or a script), but it presents several drawbacks.
 - Another difficulty is related to the fact that code's internal API
   is likely to change permanently in a research code. Thus valid
   constructions or settings may become rapidly obsolete.  In other
-  words keeping up to date embedded "data file" might be difficult.
+  words keeping up to date embedded "data files" might be difficult.
 - Finally it requires recompilation of pieces of code (which can be
   large in some cases) even if one is just changing a simple
   parameter.
@@ -391,7 +391,7 @@ it is generally to decide not to offer them.\\
 If the grammar of ~pugs~ is still likely to be extended, it should *never*
 integrate low-level instructions. Low-level instructions give too much
 freedom and thus are a source of errors. Several things are already
-done to forbid this kind of evolution. The constness of high-level
+done to forbid this kind of evolution. The immutability of high-level
 data is a good illustration. For instance, meshes or discrete
 functions *cannot* be modified. This is not only a security to protect
 the user from doing "dangerous" manipulations, but it also permits to
@@ -584,31 +584,27 @@ we give a few examples.
 #+END_SRC
 #+results: out-of-scope-variable-use
 
-**** Variable name can be reused in an enclosed scope
+**** Variable name *cannot* be reused in an enclosed scope
 #+NAME: nested-scope-variable-example
-#+BEGIN_SRC pugs :exports both :results output
-  let n:N, n = 0; // global variable
+#+BEGIN_SRC pugs-error :exports both :results output
   {
-    cout <<  "global scope n = " << n << "\n";
-    let n:N, n = 1;  // scope level 1 variable
-    {
-      cout <<  "scope level 1 n = " << n << "\n";
-      let n:N, n = 2;  // scope level 2 variable
-      cout <<  "scope level 2 n = " << n << "\n";
-    }
-    {
-      cout <<  "scope level 1 n = " << n << "\n";
-      let n:N, n = 4;  // scope level 2.2 variable
-      cout <<  "scope level 2.2 n = " << n << "\n";
-    }
-    cout <<  "scope level 1 n = " << n << "\n";
+    let n:N, n = 1;
+  }
+  {
+    let n:N, n = 2;
+  }
+  let n:N, n = 3;
+  {
+    let n:N, n = 4;
   }
-  cout << "global scope n = " << n << "\n";
 #+END_SRC
 #+results: nested-scope-variable-example
-This example is self explanatory. Obviously such constructions are
-generally *bad ideas*. This kind of constructions can appear in loops
-where the variables defined in blocks follow the same lifetime rules.
+This example is self explanatory. The same variable name can be used
+in unrelated blocks (this is useful in loops for instance). However,
+it cannot be reused if a variable has already been declared with the
+same one in an embedding scope. This is a difference with ~C++~, the
+reason is that this kind of construction is dangerous and difficult to
+read.
 
 *** Basic types<<basic-types>>
 
@@ -1602,7 +1598,7 @@ types. These can be meshes (the ~mesh~ type), output streams (the
 One can already see that the complexity of these types may vary a lot.
 
 The main difference between these types and basic types is that,
-high-level types are not available in directly the language but they
+high-level types are not available directly in the language but they
 are loaded on demand (using the ~import~ keyword in the preamble of the
 script).
 
@@ -1620,8 +1616,8 @@ operators can never be applied to variables of this kind
 | ~/=~                  | assignment by quotient      |
 
 We conclude by stating that if access operator ~[]~ can eventually be
-overloaded for high-level types, it should be done with care. It is
-not recommended.
+defined for high-level types, it should be done with care. It is not
+recommended.
 
 Finally, the last difference lies in the fact that high-level types
 use shallow copies and not value copies as it is the case for basic
@@ -2763,7 +2759,8 @@ Physical Point("XMAXYMIN") = {5};
 Physical Point("XMAXYMAX") = {6};
 #+END_SRC
 
-#+BEGIN_SRC shell :exports results :results none
+Here is an example of ~gmsh~ use that produces a mesh at format ~msh2~.
+#+BEGIN_SRC shell :exports both :results none
 gmsh -2 hybrid-2d.geo -format msh2
 #+END_SRC
 
@@ -2949,7 +2946,7 @@ The result of the previous script is given in Figure
 ***** ~relax: mesh*mesh*R -> mesh~ <<relax-function>>
 
 This function is a simple utility that computes a ~mesh~ as the /mean/ of
-two other mesh that share the same connectivity.  The coordinates of
+two other meshes that share the same connectivity.  The coordinates of
 the vertices of the relaxed mesh $\mathcal{M}_2$, are given by
 \begin{equation*}
 \forall r\in\mathcal{R},\quad\mathbf{x}_r^{\mathcal{M}_2} =  (1-\theta) \mathbf{x}_r^{\mathcal{M}_0} + \theta \mathbf{x}_r^{\mathcal{M}_1}.
@@ -3118,7 +3115,7 @@ Here is the list of the functions
 
 These functions are defined for $\mathbb{P}_0(\mathbb{R})$ data and the
 return value is also a $\mathbb{P}_0(\mathbb{R})$ function. These
-functions require that the two arguments are defined one the *same
+functions require that the two arguments are defined on the *same
 mesh*. The result is obtained by applying the function cell by cell.
 
 Here is the function list
@@ -3476,6 +3473,24 @@ $\vec{\mathbb{P}}_0(\mathbb{R})$ of dimension 1 (since passing a single
 function as a list of user function, the previous function
 [[integrate-classic]], would be used).
 
+#+BEGIN_SRC pugs :exports both :results none
+  import mesh;
+  import scheme;
+  import math;
+
+  let m:mesh, m = readGmsh("hybrid-2d.msh");
+  let u:R^2 -> R, x -> cos(x[0]*x[1]);
+  let v:R^2 -> R, x -> x[0]*x[1];
+  let w:R^2 -> R, x -> x[0]+x[1];
+
+  let u1h:Vh, u1h = integrate(m, Gauss(5), P0Vector(), u);
+  let u2h:Vh, u2h = integrate(m, Gauss(5), u);
+  let u3h:Vh, u3h = integrate(m, Gauss(5), P0Vector(), (u,v,w));
+#+END_SRC
+In this example one observes the difference between ~u1h~ and ~u2h~. The
+first one is a $\vec{\mathbb{P}}_0(\mathbb{R})$ where the vector size
+is 1, and ~u2h~ is a $\mathbb{P}_0(\mathbb{R})$.
+
 ****** ~integrate: mesh*(zone)*quadrature*function -> Vh~
 
 This function is an enhancement of the function defined in
@@ -3731,7 +3746,7 @@ descriptors that are provided by the ~scheme~ module.
 #+BEGIN_note
 These functions provide *descriptors*, these are not related to a
 particular implementation. The way they are used in different
-functions dependents of the context or the numerical method itself.
+functions may vary.
 #+END_note
 
 #+BEGIN_note
@@ -3842,9 +3857,9 @@ to supported elements.
 
 This function is a special function whose purpose is to transport
 lagrangian quantities from one mesh to the other. Obviously, this
-function make lots of sense in the case of lagrangian calculations.
+function makes a lot of sense in the case of lagrangian calculations.
 
-This is a zero cost function, since discrete functions are *constants*
+This is a zero cost function, since discrete functions are *constant*
 in ~pugs~, it consists in associating the data of the given discrete
 function to the provided ~mesh~. In other words, the underlying array of
 values is shared by the two discrete functions, which are associated
@@ -3875,13 +3890,13 @@ this order:
 - the sound speed $c$ of type $\mathbb{P}_0(\mathbb{R})$,
 - the pressure $p$ of type $\mathbb{P}_0(\mathbb{R})$,
 - a list of boundary conditions,
-- and a time step of type ~R~.
+- and a time step of type $\mathbb{R}$.
 Observe that ~pugs~ checks the types of the parameters and that all
 discrete functions are defined on the same mesh.
 
 The functions return a compound type made of
 - the new ~mesh~ $\mathcal{M}$,
-- the new mass density ~\rho~ of type $\mathbb{P}_0(\mathbb{R})$ defined
+- the new mass density $\rho$ of type $\mathbb{P}_0(\mathbb{R})$ defined
   on $\mathcal{M}$,
 - the new velocity $\mathbf{u}$ of type $\mathbb{P}_0(\mathbb{R}^d)$ in
   dimension $d$, defined on the mesh $\mathcal{M}$,
@@ -4080,7 +4095,7 @@ depends on the compilation options of the code.
 
 ****** ~getLSOptions: void -> string~
 
-This function show the current tuning of the global linear solver
+This function shows the current tuning of the global linear solver
 #+NAME: get-ls-options-example
 #+BEGIN_SRC pugs :exports both :results output
   import linear_solver;
@@ -4142,7 +4157,7 @@ different.
 
 Variables of this type manage outputs: which format is used and
 eventually the writing policy. This policy sets for instance the time
-period of for time dependent post processing.
+period for time-dependent post processing.
 
 **** ~writer~ provided functions
 
@@ -4231,7 +4246,7 @@ Let us illustrate it by an important second example.
 Running this code produces the gnuplot file displayed in Figure
 [[fig:writer-example2]]. One sees that ~f~ is the $\mathbb{P}_0(\mathbb{R})$
 function corresponding to the function $x \to x$ and not to the function
-$x -> |\cos(\pi x)|$. This later function is plotted in Figure
+$x \to |\cos(\pi x)|$. This later function is plotted in Figure
 [[fig:writer-example2-2]] since ~output_list~ is set with the updated value
 of ~fh~.
 
@@ -4248,7 +4263,7 @@ of ~fh~.
   plot '<(sed "" $PUGS_SOURCE_DIR/doc/writer-example2.gnu)' u 1:2 lw 2 t "f" w l, '<(sed "" $PUGS_SOURCE_DIR/doc/writer-example2.gnu)' u 1:3 lw 2 t "g" w l,  '<(sed "" $PUGS_SOURCE_DIR/doc/writer-example2.gnu)' u 1:4 lw 2 t "sin(pi*f)" w l
 #+END_SRC
 
-#+CAPTION: Illustration of the life time of variables. The output for ~fh~ corresponds to its value when ~output_list~ is created: the interpolation of $x -> x$.
+#+CAPTION: Illustration of the life time of variables. The output for ~fh~ corresponds to its value when ~output_list~ is created: the interpolation of $x \to x$.
 #+NAME: fig:writer-example2
 #+ATTR_LATEX: :width 0.38\textwidth
 #+ATTR_HTML: :width 300px;
@@ -4275,7 +4290,7 @@ of ~fh~.
 
 ***** ~gnuplot~ writers <<gnuplot-writers>>
 
-There is two ~gnuplot~ writers. One is dedicated to output of dimension
+There are two ~gnuplot~ writers. One is dedicated to output of dimension
 1 results (~gnuplot_1d_writer~) and the other allows post processing in
 dimension 1 and 2 (~gnuplot_writer~).
 
diff --git a/src/language/ast/ASTSymbolTableBuilder.cpp b/src/language/ast/ASTSymbolTableBuilder.cpp
index ac7d03155f60fc4a54751018f9d6cf45b5c9ec58..87baa5716cd36bd19db71e81e8e4398bfcb2e1fe 100644
--- a/src/language/ast/ASTSymbolTableBuilder.cpp
+++ b/src/language/ast/ASTSymbolTableBuilder.cpp
@@ -32,12 +32,14 @@ ASTSymbolTableBuilder::buildSymbolTable(ASTNode& n, std::shared_ptr<SymbolTable>
       throw ParseError(error_message.str(), std::vector{n.children[0]->begin()});
     }
 
-    auto [i_symbol, success] = symbol_table->add(symbol, n.children[0]->begin());
-    if (not success) {
+    if (auto [i_symbol, found] = symbol_table->find(symbol, n.children[0]->begin()); found) {
       std::ostringstream error_message;
-      error_message << "symbol '" << rang::fg::red << symbol << rang::fg::reset << "' was already defined!";
+      error_message << "symbol '" << rang::fg::red << symbol << rang::fg::reset << "' was already defined at line "
+                    << i_symbol->attributes().position().line;
       throw ParseError(error_message.str(), std::vector{n.children[0]->begin()});
     }
+    auto [i_symbol, success] = symbol_table->add(symbol, n.children[0]->begin());
+    Assert(success);
 
     for (auto& child : n.children) {
       this->buildSymbolTable(*child, local_symbol_table);
@@ -59,13 +61,13 @@ ASTSymbolTableBuilder::buildSymbolTable(ASTNode& n, std::shared_ptr<SymbolTable>
             throw ParseError(error_message.str(), std::vector{argument_node.begin()});
           }
 
-          auto [i_symbol, success] = symbol_table->add(argument_node.string(), argument_node.begin());
-          if (not success) {
+          if (auto [i_symbol, found] = symbol_table->find(argument_node.string(), argument_node.begin()); found) {
             std::ostringstream error_message;
             error_message << "symbol '" << rang::fg::red << argument_node.string() << rang::fg::reset
-                          << "' was already defined!";
+                          << "' was already defined at line " << i_symbol->attributes().position().line;
             throw ParseError(error_message.str(), std::vector{argument_node.begin()});
           }
+          symbol_table->add(argument_node.string(), argument_node.begin());
         };
 
         if (n.children[0]->is_type<language::name>()) {
@@ -91,7 +93,7 @@ ASTSymbolTableBuilder::buildSymbolTable(ASTNode& n, std::shared_ptr<SymbolTable>
           if (not success) {
             std::ostringstream error_message;
             error_message << "symbol '" << rang::fg::red << argument_node.string() << rang::fg::reset
-                          << "' was already defined!";
+                          << "' was already defined at line " << i_symbol->attributes().position().line;
             throw ParseError(error_message.str(), std::vector{argument_node.begin()});
           }
           // Symbols will be initialized at call
diff --git a/tests/test_ASTSymbolTableBuilder.cpp b/tests/test_ASTSymbolTableBuilder.cpp
index e336d7841a3fbc54903a16584d0eb224252b464d..5f5eae42fe968a730ae82b5ae347a94fa3a9a4fb 100644
--- a/tests/test_ASTSymbolTableBuilder.cpp
+++ b/tests/test_ASTSymbolTableBuilder.cpp
@@ -17,11 +17,11 @@ TEST_CASE("ASTSymbolTableBuilder", "[language]")
   SECTION("Build symbols")
   {
     std::string_view data = R"(
-let n:N, n = 2;
 {
- let m:N, m = n;
+ let m:N, m = 2;
  let n:R, n = m/3.;
 }
+let n:N, n = 2;
 )";
 
     string_input input{data, "test.pgs"};
@@ -92,7 +92,22 @@ let n:N, n = 1;
       string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
 
-      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'n' was already defined!");
+      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'n' was already defined at line 2");
+    }
+
+    SECTION("Re-declared symbol (nested scope)")
+    {
+      std::string_view data = R"(
+let n:N, n = 0;
+{
+  let n:N, n = 1;
+}
+)";
+
+      string_input input{data, "test.pgs"};
+      auto ast = ASTBuilder::build(input);
+
+      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'n' was already defined at line 2");
     }
 
     SECTION("Re-declared symbol (function)")
@@ -105,7 +120,7 @@ let f : R -> R, x -> 1;
       string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
 
-      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'f' was already defined!");
+      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'f' was already defined at line 2");
     }
 
     SECTION("Re-declared symbol (builtin function)")
@@ -140,13 +155,14 @@ let cos: R -> R, x->2*x;
     SECTION("Re-declared parameter (function)")
     {
       std::string_view data = R"(
-let f : R*R*N -> R, (x,y,x) -> 1;
+let f : R*R*N -> R,
+       (x,y,x) -> 1;
 )";
 
       string_input input{data, "test.pgs"};
       auto ast = ASTBuilder::build(input);
 
-      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'x' was already defined!");
+      REQUIRE_THROWS_WITH(ASTSymbolTableBuilder{*ast}, "symbol 'x' was already defined at line 3");
     }
   }
 }
diff --git a/tests/test_DoWhileProcessor.cpp b/tests/test_DoWhileProcessor.cpp
index 9ef421988b09c1d5639bf6d3a117d73bb02214d0..13942906c78cc81abde2ba40b64c1adb91756434 100644
--- a/tests/test_DoWhileProcessor.cpp
+++ b/tests/test_DoWhileProcessor.cpp
@@ -105,21 +105,6 @@ do {
     CHECK_WHILE_PROCESSOR_RESULT(data, "i", 12ul);
   }
 
-  SECTION("do-while lifetime variable")
-  {
-    std::string_view data = R"(
-let i:N, i = 3;
-let j:N, j = 0;
-do {
-  j = 5;
-  let j:N, j = 2;
-  i = j;
-} while(false);
-)";
-    CHECK_WHILE_PROCESSOR_RESULT(data, "i", 2ul);
-    CHECK_WHILE_PROCESSOR_RESULT(data, "j", 5ul);
-  }
-
   SECTION("empty do-while symbol table untouched")
   {
     std::string_view data = R"(
diff --git a/tests/test_WhileProcessor.cpp b/tests/test_WhileProcessor.cpp
index 028d8eae035f8743ecd01776901a493c8d743eea..1de4c6c5c4a7aa8b356a2fb77d4c211b521eedf5 100644
--- a/tests/test_WhileProcessor.cpp
+++ b/tests/test_WhileProcessor.cpp
@@ -101,21 +101,6 @@ while(i<10) {
     CHECK_WHILE_PROCESSOR_RESULT(data, "i", 12ul);
   }
 
-  SECTION("while lifetime variable")
-  {
-    std::string_view data = R"(
-let i:N, i = 3;
-let j:N, j = 0;
-while(i != 2) {
-  j = 5;
-  let j:N, j = 2;
-  i = j;
-}
-)";
-    CHECK_WHILE_PROCESSOR_RESULT(data, "i", 2ul);
-    CHECK_WHILE_PROCESSOR_RESULT(data, "j", 5ul);
-  }
-
   SECTION("while symbol table untouched")
   {
     std::string_view data = R"(