Repository: microsoft/rego-cpp (language: python)

Issue:
Failure parsing json
The attached file results in invalid nodes error.
[large.json](https://github.com/user-attachments/files/15897090/large.json)


```
> bin/rego -i large.json 1
rego 0.4.0 (HEAD:cfabe01, Tue, 18 Jun 2024 11:52:24 +0100)[Clang 14.0.0] on linux
High incidence of invalid nodes from parser: 3070 of 3206 tokens in first 4096 bytes
Invalid input file: /home/anakrish/repos/rego-compare/large.json
```


Discussion / hints:
That file is almost purpose-built to defeat the heuristic Trieste uses to detect whether a file is a valid input for a pipeline. This needs some thought. In the meantime, this is an excellent test case.

Fix patch:
diff --git a/.github/workflows/pr_gate.yml b/.github/workflows/pr_gate.yml
index 41877462..8a262cbc 100644
--- a/.github/workflows/pr_gate.yml
+++ b/.github/workflows/pr_gate.yml
@@ -165,6 +165,41 @@ jobs:
         working-directory: ${{github.workspace}}/wrappers/python/ 
         run: pytest -vv
 
+  linux-cheriot:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+        with:
+          repository: CHERIoT-Platform/cheriot-audit
+          path: cheriot-audit
+
+      - uses: mjp41/workaround8649@c8550b715ccdc17f89c8d5c28d7a48eeff9c94a8
+
+      - name: Get dependencies
+        run: |
+          sudo apt-get install ninja-build
+
+      - name: Git config for fetching pull requests
+        run: |
+          git config --global --add remote.origin.fetch +refs/pull/*/merge:refs/remotes/pull/*
+
+      - name: CMake config
+        working-directory: ${{github.workspace}}/cheriot-audit
+        run: cmake -B ${{github.workspace}}/cheriot-audit/build -DCMAKE_BUILD_TYPE=Release -G Ninja
+        env:
+          REGOCPP_REPO: https://github.com/${{github.repository}}
+          REGOCPP_TAG: ${{github.sha}}
+
+      - name: CMake build
+        working-directory: ${{github.workspace}}/cheriot-audit/build
+        run: NINJA_STATUS="%p [%f:%s/%t] %o/s, %es" && ninja
+
+      - name: CMake test
+        working-directory: ${{github.workspace}}/cheriot-audit/build
+        run: ctest -j4 --output-on-failure
+
   windows:
     runs-on: windows-latest
 
diff --git a/CHANGELOG b/CHANGELOG
index 74e8699b..ebff4781 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,15 @@
 # Changelog
 
+## 2024-06-23 - Version 0.4.1
+Point release containing doc updates and bug fixes.
+
+- Updated the docs to reflect the new interfaces and outputs
+
+**Bug Fixes**
+- Fixed an issue with actions attached to an `In` statement for some builds/environments
+- Fixed a bug with modules that start with comments
+- Fixed a bug with comprehension contexts when an assigned variable is shadowed in the body of the comprehension
+
 ## 2024-06-14 - Hardening
 Hardening for the 0.4.0 release.
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a444eb1f..0455512c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -93,7 +93,7 @@ FetchContent_Declare(
 FetchContent_Declare(
   trieste
   GIT_REPOSITORY https://github.com/microsoft/trieste
-  GIT_TAG        37b1b35fb19db799367d1ca1a82273d8d268695d
+  GIT_TAG        6bb2ccffc5b8c21c90348c07a2c8a46316faba19
 )
 
 FetchContent_MakeAvailable(cmake_utils)
diff --git a/README.md b/README.md
index 7593a406..a6b6a7ee 100644
--- a/README.md
+++ b/README.md
@@ -51,19 +51,19 @@ some example commands using the provided example files and run from the suggeste
 `dist` install directory:
 
     ./bin/rego -d examples/scalars.rego -q data.scalars.greeting
-    "Hello"
+    {"expressions":["Hello"]}
 
     ./bin/rego -d examples/objects.rego -q data.objects.sites[1].name
-    "smoke1"
+    {"expressions":["smoke1"]}
 
     ./bin/rego -d examples/data0.json examples/data1.json examples/objects.rego -i examples/input0.json  -q "[data.one, input.b, data.objects.sites[1]]"
-    [{"bar": "Foo", "baz": 5, "be": true, "bop": 23.4}, "20", {"name": "smoke1"}]
+    {"expressions":[[{"bar":"Foo", "baz":5, "be":true, "bop":23.4}, "20", {"name":"smoke1"}]]}
 
     ./bin/rego -q "5 + (2 - 4 * 0.25) * -3 + 7.4"
-    9.4
+    {"bindings":{"x":5, "y":9.4}}
 
     ./bin/rego -d examples/bodies.rego -i examples/input1.json -q data.bodies.e
-    {"one": 15, "two": 15}
+    {"expressions":[{"one":15, "two":15}]}
 
 You can run the test driver from the same directory:
 
diff --git a/VERSION b/VERSION
index 60a2d3e9..44bb5d1f 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.4.0
\ No newline at end of file
+0.4.1
\ No newline at end of file
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
index df809f96..3cd337ac 100644
--- a/doc/CMakeLists.txt
+++ b/doc/CMakeLists.txt
@@ -4,6 +4,8 @@ configure_file(
   ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY 
 )
 
+configure_file(regocpp-logo-48.png ${CMAKE_CURRENT_BINARY_DIR}/regocpp-logo-48.png COPYONLY)
+
 add_custom_target( regocpp_doc
   ALL
   ${DOXYGEN_EXECUTABLE} 
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index 4f151653..f2db807c 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -51,7 +51,7 @@ PROJECT_BRIEF          = A C++ implementation of the Rego language and runtime
 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
 # the logo to the output directory.
 
-PROJECT_LOGO           =
+PROJECT_LOGO           = regocpp-logo-48.png
 
 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
 # into which the generated documentation will be written. If a relative path is
@@ -1318,15 +1318,6 @@ HTML_COLORSTYLE_SAT    = 100
 
 HTML_COLORSTYLE_GAMMA  = 80
 
-# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
-# page will contain the date and time when the page was generated. Setting this
-# to YES can help to show when doxygen was last run and thus if the
-# documentation is up to date.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-HTML_TIMESTAMP         = NO
-
 # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
 # documentation will contain a main index with vertical navigation menus that
 # are dynamically created via JavaScript. If disabled, the navigation index will
@@ -1621,17 +1612,6 @@ HTML_FORMULA_FORMAT    = png
 
 FORMULA_FONTSIZE       = 10
 
-# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
-# generated for formulas are transparent PNGs. Transparent PNGs are not
-# supported properly for IE 6.0, but are supported on all modern browsers.
-#
-# Note that when changing this option you need to delete any form_*.png files in
-# the HTML output directory before the changes have effect.
-# The default value is: YES.
-# This tag requires that the tag GENERATE_HTML is set to YES.
-
-FORMULA_TRANSPARENT    = YES
-
 # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
 # to create new LaTeX commands to be used in formulas as building blocks. See
 # the section "Including formulas" for details.
@@ -1943,14 +1923,6 @@ LATEX_HIDE_INDICES     = NO
 
 LATEX_BIB_STYLE        = plain
 
-# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
-# page will contain the date and time when the page was generated. Setting this
-# to NO can help when comparing the output of multiple runs.
-# The default value is: NO.
-# This tag requires that the tag GENERATE_LATEX is set to YES.
-
-LATEX_TIMESTAMP        = NO
-
 # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
 # path from which the emoji images will be read. If a relative path is entered,
 # it will be relative to the LATEX_OUTPUT directory. If left blank the
@@ -2321,23 +2293,6 @@ HAVE_DOT               = YES
 
 DOT_NUM_THREADS        = 0
 
-# When you want a differently looking font in the dot files that doxygen
-# generates you can specify the font name using DOT_FONTNAME. You need to make
-# sure dot is able to find the font, which can be done by putting it in a
-# standard location or by setting the DOTFONTPATH environment variable or by
-# setting DOT_FONTPATH to the directory containing the font.
-# The default value is: Helvetica.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTNAME           = Helvetica
-
-# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
-# dot graphs.
-# Minimum value: 4, maximum value: 24, default value: 10.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_FONTSIZE           = 10
-
 # By default doxygen will tell dot to use the default font as specified with
 # DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
 # the path where dot can find it using this tag.
@@ -2574,18 +2529,6 @@ DOT_GRAPH_MAX_NODES    = 50
 
 MAX_DOT_GRAPH_DEPTH    = 0
 
-# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
-# background. This is disabled by default, because dot on Windows does not seem
-# to support this out of the box.
-#
-# Warning: Depending on the platform used, enabling this option may lead to
-# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
-# read).
-# The default value is: NO.
-# This tag requires that the tag HAVE_DOT is set to YES.
-
-DOT_TRANSPARENT        = NO
-
 # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
 # files in one run (i.e. multiple -o and -T options on the command line). This
 # makes dot run faster, but since only newer versions of dot (>1.8.10) support
diff --git a/doc/favicon.ico b/doc/favicon.ico
new file mode 100644
index 00000000..cefcf2d0
Binary files /dev/null and b/doc/favicon.ico differ
diff --git a/doc/regocpp-logo-48.png b/doc/regocpp-logo-48.png
new file mode 100755
index 00000000..663c6854
Binary files /dev/null and b/doc/regocpp-logo-48.png differ
diff --git a/doc/regocpp-logo.svg b/doc/regocpp-logo.svg
new file mode 100644
index 00000000..885830e5
--- /dev/null
+++ b/doc/regocpp-logo.svg
@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+  <rect fill="#00599C" x="0" y="0" width="24" height="24" rx="6" />
+  <g transform="translate(4, 4)">
+      <path fill="#ffffff" d="M1 0a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h5v-1H2v-1h4v-1H4v-1h2v-1H2v-1h4V9H4V8h2V7H2V6h4V2h1v4h1V4h1v2h1V2h1v4h1V4h1v2h1V2h1v4h1V1a1 1 0 0 0-1-1H1z"/>
+  </g>
+</svg>
\ No newline at end of file
diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml
index 46a85578..80797eee 100644
--- a/examples/rust/Cargo.toml
+++ b/examples/rust/Cargo.toml
@@ -6,5 +6,5 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-regorust = "0.3.11"
+regorust = "0.4.0"
 clap = { version = "4.0", features = ["derive"] }
\ No newline at end of file
diff --git a/src/parse.cc b/src/parse.cc
index da4959ac..02a24a33 100644
--- a/src/parse.cc
+++ b/src/parse.cc
@@ -63,6 +63,11 @@ namespace rego
             // ignore leading whitespace
           },
 
+        R"(#[^\r\n]*\r?\n)" >>
+          [](auto&) {
+            // ignore leading comments
+          },
+
         R"(package)" >>
           [newline_mode](auto& m) {
             m.push(Module);
diff --git a/src/reader.cc b/src/reader.cc
index 507ce46a..2dc57137 100644
--- a/src/reader.cc
+++ b/src/reader.cc
@@ -1323,8 +1323,8 @@ namespace
       wf_rules,
       dir::bottomup | dir::once,
       {
-        In(Group)([](auto& n) { return in_parent(n, Module); }) *
-            ((T(Expr)[Rule]
+        In(Group) *
+            ((T(Expr)[Rule]([](auto& n) { return in_parent(n, Module); })
               << (T(ExprInfix)
                   << ((T(Expr)
                        << (T(Term)
@@ -1360,8 +1360,8 @@ namespace
                         << (RuleBodySeq << _[RuleBodySeq]);
           },
 
-        In(Group)([](auto& n) { return in_parent(n, Module); }) *
-            ((T(Expr)[Rule]
+        In(Group) *
+            ((T(Expr)[Rule]([](auto& n) { return in_parent(n, Module); })
               << (T(Term)
                   << (T(Ref)
                       << ((T(RefHead) << T(Var)[RuleRef]) *
@@ -1407,9 +1407,9 @@ namespace
             return Rule << False << rulehead << (RuleBodySeq << _[RuleBodySeq]);
           },
 
-        In(Group)([](auto& n) { return in_parent(n, Module); }) *
+        In(Group) *
             (~T(Default)[Default] *
-             (T(Expr)[Rule]
+             (T(Expr)[Rule]([](auto& n) { return in_parent(n, Module); })
               << (T(ExprInfix)
                   << ((T(Expr) << (T(Term) << T(Ref)[RuleRef])) *
                       (T(InfixOperator) << T(AssignOperator)) *
@@ -1430,10 +1430,11 @@ namespace
                         << (RuleBodySeq << _[RuleBodySeq]);
           },
 
-        In(Group)([](auto& n) { return in_parent(n, Module); }) *
+        In(Group) *
             (~T(Default)[Default] *
-             (T(Expr)[Rule] << (T(Term) << T(Ref)[RuleRef])) * ~T(If)[If] *
-             T(Query, Else)++[RuleBodySeq] * T(NewLine)) >>
+             (T(Expr)[Rule]([](auto& n) { return in_parent(n, Module); })
+              << (T(Term) << T(Ref)[RuleRef])) *
+             ~T(If)[If] * T(Query, Else)++[RuleBodySeq] * T(NewLine)) >>
           [strict](Match& _) {
             if (*strict && _(If) == nullptr && !_[RuleBodySeq].empty())
             {
@@ -1452,9 +1453,9 @@ namespace
                         << (RuleBodySeq << _[RuleBodySeq]);
           },
 
-        In(Group)([](auto& n) { return in_parent(n, Module); }) *
+        In(Group) *
             (~T(Default)[Default] *
-             (T(Expr)[Rule]
+             (T(Expr)[Rule]([](auto& n) { return in_parent(n, Module); })
               << (T(ExprInfix)
                   << ((T(Expr)
                        << (T(ExprCall)
@@ -1479,9 +1480,9 @@ namespace
                         << (RuleBodySeq << _[RuleBodySeq]);
           },
 
-        In(Group)([](auto& n) { return in_parent(n, Module); }) *
+        In(Group) *
             (~T(Default)[Default] *
-             (T(Expr)[Rule]
+             (T(Expr)[Rule]([](auto& n) { return in_parent(n, Module); })
               << (T(ExprCall) << (T(Ref)[RuleRef] * T(ExprSeq)[RuleArgs]))) *
              ~T(If)[If] * T(Query, Else)++[RuleBodySeq] * T(NewLine)) >>
           [strict](Match& _) {
@@ -1503,10 +1504,11 @@ namespace
                         << (RuleBodySeq << _[RuleBodySeq]);
           },
 
-        In(Group)([](auto& n) { return in_parent(n, Module); }) *
-            ((T(Expr)[Rule] << (T(Term) << (T(Ref)[RuleRef]))) * T(Contains) *
-             T(Expr)[Expr] * ~T(If)[If] * T(Query, Else)++[RuleBodySeq] *
-             T(NewLine)) >>
+        In(Group) *
+            ((T(Expr)[Rule]([](auto& n) { return in_parent(n, Module); })
+              << (T(Term) << (T(Ref)[RuleRef]))) *
+             T(Contains) * T(Expr)[Expr] * ~T(If)[If] *
+             T(Query, Else)++[RuleBodySeq] * T(NewLine)) >>
           [strict](Match& _) {
             if (*strict && _(If) == nullptr && !_[RuleBodySeq].empty())
             {
diff --git a/src/unify/init.cc b/src/unify/init.cc
index fb9ade61..5ae8ab3a 100644
--- a/src/unify/init.cc
+++ b/src/unify/init.cc
@@ -70,25 +70,42 @@ namespace
 
   struct Locals
   {
-    std::vector<std::set<Location>> locals;
+    std::vector<std::map<Location, bool>> locals;
 
-    void insert(const Location& loc)
+    void insert(const Location& name, bool add_local)
     {
-      locals.back().insert(loc);
+      locals.back()[name] = add_local;
     }
 
-    void erase(const Location& loc)
+    void erase(const Location& name)
     {
-      locals.back().erase(loc);
+      locals.back().erase(name);
     }
 
-    bool contains(const Location& loc) const
+    bool contains(const Location& name) const
     {
-      return std::any_of(locals.rbegin(), locals.rend(), [loc](auto& set) {
-        return set.count(loc) > 0;
+      return std::any_of(locals.rbegin(), locals.rend(), [name](auto& m) {
+        return ::contains(m, name);
       });
     }
 
+    void add_missing_locals(
+      Node unifybody,
+      std::vector<StmtInfo>& stmts,
+      std::map<Location, std::size_t>& local_stmts)
+    {
+      for (auto& [loc, add] : locals.back())
+      {
+        if (add)
+        {
+          std::size_t i = unifybody->size();
+          unifybody << (Local << (Var ^ loc) << Undefined);
+          local_stmts[loc] = i;
+          stmts.push_back({i, {}, {}});
+        }
+      }
+    }
+
     void push()
     {
       locals.push_back({});
@@ -126,11 +143,25 @@ namespace
     return LiteralInit << lhs_vars << rhs_vars << (AssignInfix << lhs << rhs);
   }
 
-  void inits_from(Node node, const Locals& locals, std::set<Location>& inits)
+  void inits_from(
+    Node node,
+    Locals& locals,
+    std::set<Location>& inits,
+    const std::set<Location>& outer_inits)
   {
-    if (node->type() == Var && locals.contains(node->location()))
+    if (node == Var)
     {
-      inits.insert(node->location());
+      if (locals.contains(node->location()))
+      {
+        inits.insert(node->location());
+      }
+
+      if (contains(outer_inits, node->location()))
+      {
+        locals.insert(node->location(), true);
+        inits.insert(node->location());
+      }
+
       return;
     }
 
@@ -166,7 +197,7 @@ namespace
 
     for (Node child : *node)
     {
-      inits_from(child, locals, inits);
+      inits_from(child, locals, inits, outer_inits);
     }
   }
 
@@ -188,10 +219,11 @@ namespace
     }
   }
 
-  StmtSide side_from(Node node, Locals& locals)
+  StmtSide side_from(
+    Node node, Locals& locals, const std::set<Location>& outer_inits)
   {
     StmtSide side;
-    inits_from(node, locals, side.inits);
+    inits_from(node, locals, side.inits, outer_inits);
     vars_from(node, locals, side.vars);
     return side;
   }
@@ -205,7 +237,8 @@ namespace
     });
   }
 
-  void find_init_stmts(Node unifybody, Locals& locals)
+  void find_init_stmts(
+    Node unifybody, Locals& locals, const std::set<Location>& outer_inits)
   {
     logging::Trace() << std::endl
                      << "Finding init statements and re-ordering body";
@@ -222,7 +255,7 @@ namespace
         // this is a local for this context
         // as such it forms a starting point for the graph
         Location loc = (stmt / Var)->location();
-        locals.insert(loc);
+        locals.insert(loc, false);
         local_stmts[loc] = i;
         stmts.push_back({i, {}, {}});
         logging::Trace() << "Local: " << loc.view();
@@ -254,8 +287,8 @@ namespace
         Node lhs = expr->front();
         Node rhs = expr->back();
 
-        StmtSide lhs_side = side_from(lhs, locals);
-        StmtSide rhs_side = side_from(rhs, locals);
+        StmtSide lhs_side = side_from(lhs, locals, outer_inits);
+        StmtSide rhs_side = side_from(rhs, locals, outer_inits);
 
         if (any_compiler_inits(lhs_side))
         {
@@ -274,6 +307,8 @@ namespace
       }
     }
 
+    locals.add_missing_locals(unifybody, stmts, local_stmts);
+
     for (auto& stmt : stmts)
     {
       logging::Trace() << stmt;
@@ -406,7 +441,7 @@ namespace
     {
       if (stmt->in({LiteralEnum, LiteralWith, LiteralNot}))
       {
-        find_init_stmts(stmt / UnifyBody, locals);
+        find_init_stmts(stmt / UnifyBody, locals, outer_inits);
       }
     }
 
@@ -424,13 +459,13 @@ namespace
     if (body->type() == UnifyBody)
     {
       Locals locals;
-      find_init_stmts(body, locals);
+      find_init_stmts(body, locals, {});
     }
 
     if (val->type() == UnifyBody)
     {
       Locals locals;
-      find_init_stmts(val, locals);
+      find_init_stmts(val, locals, {});
     }
 
     return 0;
@@ -454,13 +489,30 @@ namespace rego
     init.pre(RuleObj, register_init_stmts);
     init.pre(NestedBody, [](Node nested) {
       Locals locals;
-      find_init_stmts(nested / Val, locals);
+      std::set<Location> init_vars;
+      Node init_parent = nested->parent(LiteralInit);
+      if (init_parent != nullptr)
+      {
+        // if this nested body is part of an init statement, then the
+        // init vars cannot be referenced within the statement.
+        Node lhs = init_parent / Lhs;
+        Node rhs = init_parent / Rhs;
+        for (Node var : *lhs)
+        {
+          init_vars.insert(var->location());
+        }
+        for (Node var : *rhs)
+        {
+          init_vars.insert(var->location());
+        }
+      }
+      find_init_stmts(nested / Val, locals, init_vars);
       return 0;
     });
 
     init.pre(ExprEvery, [](Node exprevery) {
       Locals locals;
-      find_init_stmts(exprevery / UnifyBody, locals);
+      find_init_stmts(exprevery / UnifyBody, locals, {});
       return 0;
     });
 
diff --git a/wrappers/python/README.md b/wrappers/python/README.md
index ad3cd547..73696cd2 100644
--- a/wrappers/python/README.md
+++ b/wrappers/python/README.md
@@ -25,8 +25,7 @@ within a Python context.
 from regopy import Interpreter
 rego = Interpreter()
 print(rego.query("x=5;y=x + (2 - 4 * 0.25) * -3 + 7.4"))
-# x = 5
-# y = 9.4
+# {"bindings":{"x":5, "y":9.5}}
 input0 = {
     "a": 10,
     "b": "20",
@@ -81,7 +80,7 @@ rego.add_data(data0)
 rego.add_data(data1)
 rego.add_module("objects", module)
 print(rego.query("x=[data.one, input.b, data.objects.sites[1]]"))
-# x = [{"bar":"Foo", "baz":5, "be":true, "bop":23.4}, "20", {"name":"smoke1"}]
+# {"bindings":{"x":[{"bar":"Foo", "baz":5, "be":true, "bop":23.4}, "20", {"name":"smoke1"}]}}
 ```
 
 ## Language Support
diff --git a/wrappers/python/docs/source/_static/favicon.ico b/wrappers/python/docs/source/_static/favicon.ico
new file mode 100644
index 00000000..cefcf2d0
Binary files /dev/null and b/wrappers/python/docs/source/_static/favicon.ico differ
diff --git a/wrappers/python/docs/source/_static/regocpp-logo-72.png b/wrappers/python/docs/source/_static/regocpp-logo-72.png
new file mode 100644
index 00000000..0e2ca6ef
Binary files /dev/null and b/wrappers/python/docs/source/_static/regocpp-logo-72.png differ
diff --git a/wrappers/python/docs/source/conf.py b/wrappers/python/docs/source/conf.py
index 0b7cc879..f2ad4049 100644
--- a/wrappers/python/docs/source/conf.py
+++ b/wrappers/python/docs/source/conf.py
@@ -7,9 +7,9 @@
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
 
 project = 'regopy'
-copyright = '2023, Microsoft'
+copyright = '2024, Microsoft'
 author = 'Microsoft'
-release = '0.3.11'
+release = '0.4.0'
 
 # -- General configuration ---------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -34,3 +34,5 @@
 
 html_theme = 'alabaster'
 html_static_path = ['_static']
+html_logo = "_static/regocpp-logo-72.png"
+html_favicon = "_static/favicon.ico"


Test patch:
diff --git a/tests/bugs.yaml b/tests/bugs.yaml
index e9a6e040..9747fa35 100644
--- a/tests/bugs.yaml
+++ b/tests/bugs.yaml
@@ -225,3 +225,43 @@ cases:
   query: "[1, 2][1/1] = x"
   want_result:
     - x: 2
+- note: regocpp/bug146
+  modules:
+  - |
+    package compr_context
+
+    import future.keywords.in
+
+    output := a {
+      a := {x: a |
+        some x in [0, 1, 2]
+        a = x * 2
+      }
+    }
+  query: "data.compr_context.output = x"
+  want_result:
+  - x:
+      0: 0
+      1: 2
+      2: 4
+- note: regocpp/bug151
+  modules:
+  - |
+    #
+    package test
+  - |
+    # some comment text
+    package test
+
+    p := 1
+  - |
+    #
+    # comment after empty
+    package test
+
+    q := 2
+  query: "data.test = x"
+  want_result:
+  - x:
+      p: 1
+      q: 2