Merge pull request #1578 from antmicro/module-ports

Add helper functions for `kModulePortDeclaration`s
diff --git a/.github/bin/build-and-test.sh b/.github/bin/build-and-test.sh
index aca1978..f059ac7 100755
--- a/.github/bin/build-and-test.sh
+++ b/.github/bin/build-and-test.sh
@@ -83,6 +83,10 @@
     ;;
 
   asan|asan-clang)
+    if [[ "${MODE}" == "asan" ]]; then
+      # Some gcc 12 issue with regexp it seems.
+      BAZEL_OPTS="${BAZEL_OPTS} --cxxopt=-Wno-maybe-uninitialized"
+    fi
     bazel test --config=asan --cache_test_results=no --test_output=errors $BAZEL_OPTS -c fastbuild ${CHOSEN_TARGETS}
     ;;
 
@@ -98,6 +102,11 @@
     bazel build --keep_going $BAZEL_OPTS :install-binaries
     ;;
 
+  compile20)
+    # Compile with C++ 20 to make sure to be compatible with the next version.
+    bazel build --keep_going $BAZEL_OPTS --cxxopt=-std=c++20 :install-binaries
+    ;;
+
   smoke-test)
     $(dirname $0)/smoke-test.sh
     ;;
diff --git a/.github/bin/github-pages-setup.sh b/.github/bin/github-pages-setup.sh
index 887b16b..d1c1bea 100755
--- a/.github/bin/github-pages-setup.sh
+++ b/.github/bin/github-pages-setup.sh
@@ -17,11 +17,21 @@
 # ---------------
 # Generate the GitHub pages to deploy
 
+set -eux
+
+BINDIR=${1:-/usr/bin}
 REPO_SLUG=${REPO_SLUG:-${GITHUB_REPOSITORY_SLUG:-google/verible}}
 GIT_DATE=${GIT_DATE:-$(git show -s --format=%ci)}
 GIT_HASH=${GIT_HASH:-$(git rev-parse HEAD)}
 GIT_VERSION=${GIT_VERSION:-$(git rev-parse --short "$GITHUB_SHA")}
 
+# Check if a file exists and is executable before running it.
+# Useful for running commands which return a non-zero exit code 
+# despite providing meaningful output, e.g. Verible binaries with the "-helpfull" param.
+check_and_run() {
+    test -x "$1" && ($@ || true)
+}
+
 echo "Git date:    $GIT_DATE"
 echo "Git hash:    $GIT_HASH"
 echo "Git version: $GIT_VERSION"
@@ -77,7 +87,7 @@
 
 # Generate lint rules documentation
 # --------------------------------
-bazel-bin/verilog/tools/lint/verible-verilog-lint -generate_markdown > $PAGES_DIR/lint.md
+${BINDIR}/verible-verilog-lint -generate_markdown > $PAGES_DIR/lint.md
 cat >> $PAGES_DIR/lint.md <<EOF
 
 ## Version
@@ -97,7 +107,7 @@
 ## Command line arguments
 \`\`\`
 EOF
-bazel-bin/verilog/tools/syntax/verible-verilog-syntax -helpfull >> $SYNTAX_DOC
+check_and_run ${BINDIR}/verible-verilog-syntax -helpfull >> $SYNTAX_DOC
 cat >> $SYNTAX_DOC <<EOF
 \`\`\`
 
@@ -120,14 +130,14 @@
 
 \`\`\`
 EOF
-bazel-bin/verilog/tools/lint/verible-verilog-lint -helpfull >> $LINT_DOC
+check_and_run ${BINDIR}/verible-verilog-lint -helpfull >> $LINT_DOC
 cat >> $LINT_DOC <<'EOF'
 ```
 
 ## Lint Rules
 
 EOF
-bazel-bin/verilog/tools/lint/verible-verilog-lint -generate_markdown >> $LINT_DOC
+${BINDIR}/verible-verilog-lint -generate_markdown >> $LINT_DOC
 cat >> $LINT_DOC <<EOF
 
 ## Version
@@ -147,7 +157,7 @@
 ## Command line arguments
 \`\`\`
 EOF
-bazel-bin/verilog/tools/formatter/verible-verilog-format -helpfull >> $FORMAT_DOC
+check_and_run ${BINDIR}/verible-verilog-format -helpfull >> $FORMAT_DOC
 cat >> $FORMAT_DOC <<EOF
 \`\`\`
 
diff --git a/.github/bin/github-releases-setup.sh b/.github/bin/github-releases-setup.sh
index 9490ab7..06ca975 100755
--- a/.github/bin/github-releases-setup.sh
+++ b/.github/bin/github-releases-setup.sh
@@ -17,8 +17,11 @@
 # ---------------
 # Generate the GitHub Releases to deploy
 
+set -ex
+
+SCRIPT_DIR=$(dirname $0)
 RELEASE_DIR=${1:-/tmp/releases}
-rm -rf $RELEASE_DIR
+rm -rf "$RELEASE_DIR/*"
 
 GIT_VERSION=${GIT_VERSION:-$(git rev-parse --short "$GITHUB_SHA")}
 
@@ -36,9 +39,11 @@
 for BIN in $PREFIX_BIN/*; do
     ls -l $BIN
     file $BIN
-    ldd $BIN
+    ldd $BIN || true # ignore the potential 'not a dynamic executable' errors for non-binaries
 done
 
+${SCRIPT_DIR}/github-pages-setup.sh $PREFIX_BIN
+
 # Documentation
 cp -a /tmp/pages/* $PREFIX_DOC
 # Man pages
diff --git a/.github/bin/install-bazel.sh b/.github/bin/install-bazel.sh
index 612456d..af67ec3 100755
--- a/.github/bin/install-bazel.sh
+++ b/.github/bin/install-bazel.sh
@@ -18,6 +18,6 @@
         exit 1
 fi
 wget --no-verbose "https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel_${BAZEL_VERSION}-linux-x86_64.deb" -O /tmp/bazel.deb
-sudo dpkg -i /tmp/bazel.deb || true
-sudo apt-get -f install
+dpkg -i /tmp/bazel.deb || true
+apt-get -f install
 bazel --version
diff --git a/.github/bin/run-clang-format.sh b/.github/bin/run-clang-format.sh
index 73ac1d1..2fdfb04 100755
--- a/.github/bin/run-clang-format.sh
+++ b/.github/bin/run-clang-format.sh
@@ -24,7 +24,10 @@
 
 if [ -s ${FORMAT_OUT} ]; then
    echo "Style not matching (see https://github.com/chipsalliance/verible/blob/master/CONTRIBUTING.md#style)"
-   echo "On your *.h, *.cc files, please run clang-format -i --style=file <your changed files>"
+   echo "Run"
+   echo "  .github/bin/run-clang-format.sh"
+   echo "-------------------------------------------------"
+   echo
    cat ${FORMAT_OUT}
    exit 1
 fi
diff --git a/.github/bin/set-compiler.sh b/.github/bin/set-compiler.sh
index b2c535a..99d007f 100755
--- a/.github/bin/set-compiler.sh
+++ b/.github/bin/set-compiler.sh
@@ -15,12 +15,12 @@
 
 VERSION=$1
 
-sudo dpkg --list | grep gcc
-sudo dpkg --list | grep libstdc++
+dpkg --list | grep gcc
+dpkg --list | grep libstdc++
 
-sudo ln -sf /usr/bin/gcc-$VERSION /usr/bin/gcc
-sudo ln -sf /usr/bin/g++-$VERSION /usr/bin/g++
-sudo ln -sf /usr/bin/gcov-$VERSION /usr/bin/gcov
+ln -sf /usr/bin/gcc-$VERSION /usr/bin/gcc
+ln -sf /usr/bin/g++-$VERSION /usr/bin/g++
+ln -sf /usr/bin/gcov-$VERSION /usr/bin/gcov
 
 gcc --version || true
 gcov --version
diff --git a/.github/bin/smoke-test.sh b/.github/bin/smoke-test.sh
index bda322c..1e96d74 100755
--- a/.github/bin/smoke-test.sh
+++ b/.github/bin/smoke-test.sh
@@ -72,7 +72,7 @@
 # array below, mapping them to Verible issue tracker numbers.
 readonly TEST_GIT_PROJECTS="https://github.com/lowRISC/ibex \
          https://github.com/lowRISC/opentitan \
-         https://github.com/chipsalliance/Cores-SweRV \
+         https://github.com/chipsalliance/Cores-VeeR-EH2 \
          https://github.com/openhwgroup/cva6 \
          https://github.com/SymbiFlow/uvm \
          https://github.com/taichi-ishitani/tnoc \
@@ -100,12 +100,11 @@
 declare -A KnownIssue
 
 #--- Opentitan
-KnownIssue[formatter:$BASE_TEST_DIR/opentitan/hw/ip/aes/dv/aes_model_dpi/aes_model_dpi_pkg.sv]=1006
+# Original issue - 1006 has been resolved
 # There is also bug 1008 which only shows up if compiled with asan
 
 #--- ivtest
 KnownIssue[formatter:$BASE_TEST_DIR/ivtest/ivltests/pr2202846c.v]=1015
-KnownIssue[formatter:$BASE_TEST_DIR/ivtest/ivltests/packed_dims_invalid_class.v]=1146
 
 #--- nontrivial-mips
 KnownIssue[formatter:$BASE_TEST_DIR/nontrivial-mips/src/cpu/decode/decoder.sv]=984
@@ -132,18 +131,19 @@
 
 ExpectedFailCount[syntax:ibex]=14
 ExpectedFailCount[lint:ibex]=14
-ExpectedFailCount[project:ibex]=189
+ExpectedFailCount[project:ibex]=192
 
 ExpectedFailCount[syntax:opentitan]=35
 ExpectedFailCount[lint:opentitan]=35
-ExpectedFailCount[formatter:opentitan]=1
-ExpectedFailCount[project:opentitan]=722
+ExpectedFailCount[project:opentitan]=727
 
-ExpectedFailCount[project:Cores-SweRV]=21
+ExpectedFailCount[syntax:Cores-VeeR-EH2]=2
+ExpectedFailCount[lint:Cores-VeeR-EH2]=2
+ExpectedFailCount[project:Cores-VeeR-EH2]=42
 
 ExpectedFailCount[syntax:cva6]=4
 ExpectedFailCount[lint:cva6]=4
-ExpectedFailCount[project:cva6]=25
+ExpectedFailCount[project:cva6]=70
 
 ExpectedFailCount[syntax:uvm]=1
 ExpectedFailCount[lint:uvm]=1
@@ -168,7 +168,7 @@
 ExpectedFailCount[formatter:ivtest]=2
 ExpectedFailCount[project:ivtest]=217
 
-ExpectedFailCount[project:ZAP]=30
+ExpectedFailCount[project:ZAP]=29
 
 ExpectedFailCount[syntax:nontrivial-mips]=2
 ExpectedFailCount[lint:nontrivial-mips]=2
@@ -185,9 +185,9 @@
 ExpectedFailCount[formatter:scr1]=2
 ExpectedFailCount[project:scr1]=45
 
-ExpectedFailCount[syntax:basejump_stl]=460
-ExpectedFailCount[lint:basejump_stl]=460
-ExpectedFailCount[project:basejump_stl]=566
+ExpectedFailCount[syntax:basejump_stl]=462
+ExpectedFailCount[lint:basejump_stl]=462
+ExpectedFailCount[project:basejump_stl]=569
 
 # Ideally, we expect all tools to process all files with a zero exit code.
 # However, that is not always the case, so we document the current
diff --git a/.github/workflows/verible-ci.yml b/.github/workflows/verible-ci.yml
index fadddd6..778829c 100644
--- a/.github/workflows/verible-ci.yml
+++ b/.github/workflows/verible-ci.yml
@@ -12,15 +12,18 @@
   workflow_dispatch:
 
 env:
-  DOCKER_BUILDKIT: 1
   BOT_USER: "Deployment Bot"
   BOT_EMAIL: "verible-dev@googlegroups.com"
+  GHA_MACHINE_TYPE: "n2-standard-8"
+  DEBIAN_FRONTEND: "noninteractive"
+  VERIBLE_BINDIR: "/tmp/verible-bin"
 
 jobs:
 
 
   VerifyFormatting:
-    runs-on: ubuntu-20.04
+    container: ubuntu:20.04
+    runs-on: [self-hosted, Linux, X64]
 
     steps:
 
@@ -36,14 +39,26 @@
 
     - name: Install Dependencies
       run: |
-        sudo apt -qq -y install clang-format
+        apt -qqy update
+        apt -qq -y install clang-format git
         clang-format --version
 
     - name: Run formatting style check
       run: ./.github/bin/run-clang-format.sh
 
+    - name: 📤 Upload performance graphs
+      uses: actions/upload-artifact@v2
+      if: success() || failure()
+      with:
+        name: "diag"
+        path: "**/plot_*.svg"
+
   ClangTidy:
-    runs-on: ubuntu-20.04
+    container: ubuntu:20.04
+    runs-on: [self-hosted, Linux, X64]
+
+    env:
+      GHA_MACHINE_TYPE: "n2-standard-4"
 
     steps:
 
@@ -59,7 +74,10 @@
 
     - name: Install Dependencies
       run: |
-        sudo apt -qq -y install clang-tidy-11
+        apt -qqy update
+        apt -qq -y install clang-tidy-11 build-essential git wget
+        source ./.github/settings.sh
+        ./.github/bin/install-bazel.sh
         echo "TMPDIR=/tmp" >> $GITHUB_ENV
 
     - name: Create Cache Timestamp
@@ -69,20 +87,28 @@
         format: 'YYYY-MM-DD-HH-mm-ss'
 
     - name: Retrieve cached results
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       with:
         path: |
           /tmp/clang-tidy-hashes.cache
-          /home/runner/.cache/bazel
+          /root/.cache/bazel
         key: clang-tidy-${{ steps.cache_timestamp.outputs.time }}
         restore-keys: clang-tidy-
 
     - name: Run clang tidy
       run: ./.github/bin/run-clang-tidy.sh
 
+    - name: 📤 Upload performance graphs
+      uses: actions/upload-artifact@v2
+      if: success() || failure()
+      with:
+        name: "diag"
+        path: "**/plot_*.svg"
+
 
   Check:
-    runs-on: ubuntu-20.04
+    container: ubuntu:20.04
+    runs-on: [self-hosted, Linux, X64]
     strategy:
       fail-fast: false
       matrix:
@@ -119,45 +145,52 @@
         format: 'YYYY-MM-DD-HH-mm-ss'
 
     - name: Mount bazel cache
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       if: matrix.mode != 'clean' && matrix.mode != 'coverage'
       with:
-        path: "/home/runner/.cache/bazel"
+        path: "/root/.cache/bazel"
         key: bazelcache_${{ matrix.mode }}_${{ steps.cache_timestamp.outputs.time }}
         restore-keys: bazelcache_${{ matrix.mode }}_
 
     - name: Install Dependencies
       run: |
         set -x
+        apt -qqy update
+        apt -qq -y install clang-10 build-essential wget git python3 python-is-python3 gcc-10 g++-10 default-jdk
         source ./.github/settings.sh
-        sudo apt -qq -y install clang-10
-        ./.github/bin/set-compiler.sh 9
+        ./.github/bin/set-compiler.sh 10
         ./.github/bin/install-bazel.sh
 
     - name: ${{ matrix.mode }} Verible
       run: ./.github/bin/build-and-test.sh
 
-    - name: Set up things for GitHub Pages deployment
-      if: matrix.mode == 'compile'
-      run: ./.github/bin/github-pages-setup.sh
-
     - name: Upload coverage
       # will show up under https://app.codecov.io/gh/chipsalliance/verible
       if: matrix.mode == 'coverage'
       uses: codecov/codecov-action@v2
       with:
         files: bazel-out/_coverage/_coverage_report.dat
-        fail_ci_if_error: true
         verbose: true
+      continue-on-error: true
 
-    - name: Deployment
-      if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && matrix.mode == 'compile'
-      uses: JamesIves/github-pages-deploy-action@3.7.1
+    - name: Gather and pack binaries
+      if: matrix.mode == 'compile'
+      run: bazel run :install -c fastbuild -- $VERIBLE_BINDIR && tar cfv verible-bin.tar -C $VERIBLE_BINDIR .
+
+    - name: Upload bazel-bin 
+      if: matrix.mode == 'compile'
+      uses: actions/upload-artifact@v2
       with:
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        BRANCH: gh-pages
-        FOLDER: /tmp/pages
-        CLEAN: true
+        name: verible-bin
+        path: verible-bin.tar
+
+
+    - name: 📤 Upload performance graphs
+      uses: actions/upload-artifact@v2
+      if: success() || failure()
+      with:
+        name: "diag"
+        path: "**/plot_*.svg"
 
 
   Kythe:
@@ -207,7 +240,8 @@
 
   Build:
     needs: Matrix
-    runs-on: ubuntu-20.04
+    container: debian:bullseye
+    runs-on: [self-hosted, Linux, X64]
     # Github actions resources are limited; don't build artifacts for all
     # platforms on every pull request, only on push.
     if: ${{github.event_name == 'push'}}
@@ -217,6 +251,8 @@
         include: ${{ fromJson(needs.Matrix.outputs.matrix) }}
     env:
       MATRIX_OS: '${{ matrix.os }}:${{ matrix.ver }}'
+      DOCKER_DATA_ROOT: "/root/.docker"
+      GHA_MACHINE_TYPE: "n2-highcpu-8"
     name: 'Build · ${{ matrix.os }}:${{ matrix.ver }}'
 
     steps:
@@ -226,11 +262,24 @@
        # Download complete repository + tags
        fetch-depth: 0
 
-    - run: docker pull $MATRIX_OS
+    - name: Install and setup Docker
+      run: "apt -qqy update && apt -qqy --no-install-recommends install docker.io cgroupfs-mount crun fuse-overlayfs pigz ca-certificates git && cgroupfs-mount"
 
     - name: Main script
       run: |
         set -x
+
+        mkdir -p "$DOCKER_DATA_ROOT"
+        dockerd \
+          -s fuse-overlayfs \
+          --add-runtime=crun=/usr/bin/crun \
+          --default-runtime=crun \
+          --config-file="" \
+          --data-root=$DOCKER_DATA_ROOT > /dev/null 2>&1 &
+        while ! test -S /var/run/docker.sock; do echo "Waiting for Docker..." && sleep 1; done; docker info
+        trap "kill $(cat /var/run/docker.pid)" EXIT
+
+        docker pull $MATRIX_OS
         source ./.github/settings.sh
         ./releasing/docker-run.sh $MATRIX_OS
 
@@ -239,6 +288,13 @@
       with:
         path: releasing/out/verible-*.tar.gz
 
+    - name: 📤 Upload performance graphs
+      uses: actions/upload-artifact@v2
+      if: success() || failure()
+      with:
+        name: "diag"
+        path: "**/plot_*.svg"
+
   MacOsBuild:
     runs-on: macos-latest
     steps:
@@ -254,7 +310,7 @@
         format: 'YYYY-MM-DD-HH-mm-ss'
 
     - name: Mount bazel cache
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       with:
         path: "/private/var/tmp/_bazel_runner"
         key: bazelcache_macos_${{ steps.cache_timestamp.outputs.time }}
@@ -279,7 +335,7 @@
         format: 'YYYY-MM-DD-HH-mm-ss'
 
     - name: Mount bazel cache
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       with:
         path: "c:/users/runneradmin/_bazel_runneradmin"
         key: bazelcache_windows2_${{ steps.cache_timestamp.outputs.time }}
@@ -356,3 +412,29 @@
         tag: ${{ env.TAG }}
         overwrite: true
         file_glob: true
+
+  GhPages:
+    name: Build and deploy GitHub pages
+    needs: [Check]
+    runs-on: ubuntu-22.04
+    steps:
+      - uses: actions/checkout@v3
+
+      - uses: actions/download-artifact@v3
+        with:
+          name: verible-bin
+
+      - name: Extract Verible binaries
+        run: mkdir -p $VERIBLE_BINDIR && tar xvf verible-bin.tar -C $VERIBLE_BINDIR
+
+      - name: Set up things for GitHub Pages deployment
+        run: ./.github/bin/github-pages-setup.sh $VERIBLE_BINDIR
+
+      - name: Deployment
+        if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && matrix.mode == 'compile'
+        uses: JamesIves/github-pages-deploy-action@3.7.1
+        with:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          BRANCH: gh-pages
+          FOLDER: /tmp/pages
+          CLEAN: true
diff --git a/README.md b/README.md
index 0f0cb5b..7c08a24 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 
 
 [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
-[![Continuous Integration](https://github.com/chipsalliance/verible/workflows/verible-ci/badge.svg)](https://github.com/chipsalliance/verible/actions/workflows/verible-ci.yml)
+[![Continuous Integration](https://github.com/chipsalliance/verible/workflows/ci/badge.svg)](https://github.com/chipsalliance/verible/actions/workflows/verible-ci.yml)
 [![codecov](https://codecov.io/gh/chipsalliance/verible/branch/master/graph/badge.svg?token=5f656dpmDT)](https://codecov.io/gh/chipsalliance/verible)
 
 <!--*
diff --git a/WORKSPACE b/WORKSPACE
index ad7d61c..dcbc98d 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -6,12 +6,25 @@
 # Bazel platform rules, needed as dependency to absl.
 http_archive(
     name = "platforms",
-    sha256 = "a879ea428c6d56ab0ec18224f976515948822451473a80d06c2e50af0bbe5121",
-    strip_prefix = "platforms-da5541f26b7de1dc8e04c075c99df5351742a4a2",
-    urls = ["https://github.com/bazelbuild/platforms/archive/da5541f26b7de1dc8e04c075c99df5351742a4a2.zip"],  # 2022-05-27
+    urls = [
+        "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.6/platforms-0.0.6.tar.gz",
+        "https://github.com/bazelbuild/platforms/releases/download/0.0.6/platforms-0.0.6.tar.gz",
+    ],
+    sha256 = "5308fc1d8865406a49427ba24a9ab53087f17f5266a7aabbfc28823f3916e1ca",
 )
 
 http_archive(
+    name = "bazel_skylib",
+    urls = [
+        "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
+        "https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
+    ],
+    sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
+)
+load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
+bazel_skylib_workspace()
+
+http_archive(
     name = "com_google_absl",
     # On MSVC's STL implementation, string_view cannot be constructed from
     # a string_view::iterator. This patch forces the use of absl's string_view
@@ -74,9 +87,9 @@
 
 win_flex_configure(
     name = "win_flex_bison",
-    sha256 = "095cf65cb3f12ee5888022f93109acbe6264e5f18f6ffce0bda77feb31b65bd8",
-    # bison 3.3.2, flex 2.6.4
-    url = "https://github.com/lexxmark/winflexbison/releases/download/v2.5.18/win_flex_bison-2.5.18.zip",
+    sha256 = "8d324b62be33604b2c45ad1dd34ab93d722534448f55a16ca7292de32b6ac135",
+    # bison 3.8.2, flex 2.6.4
+    url = "https://github.com/lexxmark/winflexbison/releases/download/v2.5.25/win_flex_bison-2.5.25.zip",
 )
 
 http_archive(
@@ -113,10 +126,10 @@
 # A version slightly beyond 21.5 as it fixes a warning
 http_archive(
     name = "com_google_protobuf",
-    sha256 = "efcd93bb1a228d08b4b0fcda00e710592fdf17ce6d0f5c8a239c2db0ba268a3f",
-    strip_prefix = "protobuf-4efbcc44605731dc31507b94f0b81d2bd5ec169b",
+    sha256 = "6a31b662deaeb0ac35e6287bda2f3369b19836e6c9f8828d4da444346f420298",
+    strip_prefix = "protobuf-21.12",
     urls = [
-        "https://github.com/protocolbuffers/protobuf/archive/4efbcc44605731dc31507b94f0b81d2bd5ec169b.zip",
+        "https://github.com/protocolbuffers/protobuf/archive/refs/tags/v21.12.zip",
     ],
 )
 
diff --git a/common/formatting/layout_optimizer.cc b/common/formatting/layout_optimizer.cc
index ee18ab7..39e599d 100644
--- a/common/formatting/layout_optimizer.cc
+++ b/common/formatting/layout_optimizer.cc
@@ -570,10 +570,14 @@
           }
         }
       }
+      // std::max(hanging_indentation, 0) serves to resolve a case where
+      // the subsequent nodes will try to be indented below zero following
+      // a less-indented token eg. a macro
       const int hanging_indentation =
           (node.Children().size() > 1)
-              ? (node.Children()[1].Value().IndentationSpaces() -
-                 node.Value().IndentationSpaces())
+              ? std::max(node.Children()[1].Value().IndentationSpaces() -
+                             node.Value().IndentationSpaces(),
+                         0)
               : 0;
 
       return factory_.Wrap(layouts.begin(), layouts.end(), false,
diff --git a/releasing/build.sh b/releasing/build.sh
index 2263203..008fd97 100755
--- a/releasing/build.sh
+++ b/releasing/build.sh
@@ -18,5 +18,4 @@
 
 cd $(dirname "$0")/..
 
-./.github/bin/github-pages-setup.sh
 ./.github/bin/github-releases-setup.sh /out
diff --git a/verilog/analysis/symbol_table.cc b/verilog/analysis/symbol_table.cc
index 9c804e3..3ee0ab4 100644
--- a/verilog/analysis/symbol_table.cc
+++ b/verilog/analysis/symbol_table.cc
@@ -909,12 +909,8 @@
   SymbolTableNode* EmplaceElementInCurrentScope(const verible::Symbol& element,
                                                 absl::string_view name,
                                                 SymbolMetaType metatype) {
-    const auto p =
-        current_scope_->TryEmplace(name, SymbolInfo{
-                                             .metatype = metatype,
-                                             .file_origin = source_,
-                                             .syntax_origin = &element,
-                                         });
+    const auto p = current_scope_->TryEmplace(
+        name, SymbolInfo{metatype, source_, &element});
     if (!p.second) {
       DiagnoseSymbolAlreadyExists(name, p.first->first);
     }
@@ -931,14 +927,11 @@
     VLOG(1) << "  type info: " << *ABSL_DIE_IF_NULL(declaration_type_info_);
     VLOG(1) << "  full text: " << AutoTruncate{StringSpanOfSymbol(element), 40};
     const auto p = current_scope_->TryEmplace(
-        name,
-        SymbolInfo{
-            .metatype = metatype,
-            .file_origin = source_,
-            .syntax_origin = &element,
-            // associate this instance with its declared type
-            .declared_type = *ABSL_DIE_IF_NULL(declaration_type_info_),  // copy
-        });
+        name, SymbolInfo{
+                  metatype, source_, &element,
+                  // associate this instance with its declared type
+                  *ABSL_DIE_IF_NULL(declaration_type_info_),  // copy
+              });
     if (!p.second) {
       DiagnoseSymbolAlreadyExists(name, p.first->first);
     }
@@ -1253,11 +1246,7 @@
     const absl::string_view inner_key = inner_ref.identifier;
 
     const auto p = outer_scope->TryEmplace(
-        inner_key, SymbolInfo{
-                       .metatype = metatype,
-                       .file_origin = source_,
-                       .syntax_origin = definition_syntax,
-                   });
+        inner_key, SymbolInfo{metatype, source_, definition_syntax});
     SymbolTableNode* inner_symbol = &p.first->second;
     if (p.second) {
       // If injection succeeded, then the outer_scope did not already contain a
diff --git a/verilog/analysis/symbol_table.h b/verilog/analysis/symbol_table.h
index 486b566..1cc5ef1 100644
--- a/verilog/analysis/symbol_table.h
+++ b/verilog/analysis/symbol_table.h
@@ -180,6 +180,9 @@
 
  public:
   DependentReferences() = default;
+  explicit DependentReferences(
+      std::unique_ptr<ReferenceComponentNode> components)
+      : components(std::move(components)) {}
   // move-only
   DependentReferences(const DependentReferences&) = delete;
   DependentReferences(DependentReferences&&) = default;
@@ -251,14 +254,6 @@
   bool implicit = false;
 
  public:
-  DeclarationTypeInfo() = default;
-
-  // copy-able, move-able, assignable
-  DeclarationTypeInfo(const DeclarationTypeInfo&) = default;
-  DeclarationTypeInfo(DeclarationTypeInfo&&) = default;
-  DeclarationTypeInfo& operator=(const DeclarationTypeInfo&) = default;
-  DeclarationTypeInfo& operator=(DeclarationTypeInfo&&) = default;
-
   // Structural consistency check.
   void VerifySymbolTableRoot(const SymbolTableNode* root) const;
 };
@@ -328,6 +323,13 @@
 
  public:  // methods
   SymbolInfo() = default;
+  SymbolInfo(SymbolMetaType metatype, const VerilogSourceFile* file_origin = {},
+             const verible::Symbol* syntax_origin = {},
+             DeclarationTypeInfo declared_type = {})
+      : metatype(metatype),
+        file_origin(file_origin),
+        syntax_origin(syntax_origin),
+        declared_type(declared_type) {}
 
   // move-only
   SymbolInfo(const SymbolInfo&) = delete;
@@ -422,7 +424,7 @@
   // and string memory, otherwise string memory is owned by 'project'.
   explicit SymbolTable(VerilogProject* project)
       : project_(project),
-        symbol_table_root_(SymbolInfo{.metatype = SymbolMetaType::kRoot}) {}
+        symbol_table_root_(SymbolInfo{SymbolMetaType::kRoot}) {}
 
   // can become move-able when needed
   SymbolTable(const SymbolTable&) = delete;
diff --git a/verilog/analysis/symbol_table_test.cc b/verilog/analysis/symbol_table_test.cc
index 27864d3..444fcc5 100644
--- a/verilog/analysis/symbol_table_test.cc
+++ b/verilog/analysis/symbol_table_test.cc
@@ -245,12 +245,11 @@
 }
 
 TEST(DependentReferencesTest, PrintOnlyRootNodeUnresolved) {
-  const DependentReferences dep_refs{
-      .components = std::make_unique<ReferenceComponentNode>(
-          ReferenceComponent{.identifier = "foo",
-                             .ref_type = ReferenceType::kUnqualified,
-                             .required_metatype = SymbolMetaType::kUnspecified,
-                             .resolved_symbol = nullptr})};
+  const DependentReferences dep_refs{std::make_unique<ReferenceComponentNode>(
+      ReferenceComponent{.identifier = "foo",
+                         .ref_type = ReferenceType::kUnqualified,
+                         .required_metatype = SymbolMetaType::kUnspecified,
+                         .resolved_symbol = nullptr})};
   std::ostringstream stream;
   stream << dep_refs;
   EXPECT_EQ(stream.str(), "{ (@foo -> <unresolved>) }");
@@ -260,29 +259,27 @@
   // Synthesize a symbol table.
   typedef SymbolTableNode::key_value_type KV;
   SymbolTableNode root(
-      SymbolInfo{.metatype = SymbolMetaType::kRoot},
+      SymbolInfo{SymbolMetaType::kRoot},
       KV{"p_pkg",
-         SymbolTableNode(
-             SymbolInfo{.metatype = SymbolMetaType::kPackage},
-             KV{"c_class", SymbolTableNode(SymbolInfo{
-                               .metatype = SymbolMetaType::kClass})})});
+         SymbolTableNode(SymbolInfo{SymbolMetaType::kPackage},
+                         KV{"c_class", SymbolTableNode(SymbolInfo{
+                                           SymbolMetaType::kClass})})});
 
   // Bookmark symbol table nodes.
   MUST_ASSIGN_LOOKUP_SYMBOL(p_pkg, root, "p_pkg");
   MUST_ASSIGN_LOOKUP_SYMBOL(c_class, p_pkg, "c_class");
 
   // Construct references already resolved to above nodes.
-  const DependentReferences dep_refs{
-      .components = std::make_unique<ReferenceComponentNode>(
-          ReferenceComponent{.identifier = "p_pkg",
-                             .ref_type = ReferenceType::kUnqualified,
-                             .required_metatype = SymbolMetaType::kPackage,
-                             .resolved_symbol = &p_pkg},
-          ReferenceComponentNode(
-              ReferenceComponent{.identifier = "c_class",
-                                 .ref_type = ReferenceType::kDirectMember,
-                                 .required_metatype = SymbolMetaType::kClass,
-                                 .resolved_symbol = &c_class}))};
+  const DependentReferences dep_refs{std::make_unique<ReferenceComponentNode>(
+      ReferenceComponent{.identifier = "p_pkg",
+                         .ref_type = ReferenceType::kUnqualified,
+                         .required_metatype = SymbolMetaType::kPackage,
+                         .resolved_symbol = &p_pkg},
+      ReferenceComponentNode(
+          ReferenceComponent{.identifier = "c_class",
+                             .ref_type = ReferenceType::kDirectMember,
+                             .required_metatype = SymbolMetaType::kClass,
+                             .resolved_symbol = &c_class}))};
 
   // Print and compare.
   std::ostringstream stream;
@@ -398,12 +395,11 @@
     // symbol_table1 will outlive symbol_table_2, so give symbol_table_2 a
     // pointer to symbol_table_1.
     root2.Value().local_references_to_bind.push_back(DependentReferences{
-        .components =
-            std::make_unique<ReferenceComponentNode>(ReferenceComponent{
-                .identifier = "foo",
-                .ref_type = ReferenceType::kUnqualified,
-                .required_metatype = SymbolMetaType::kUnspecified,
-                .resolved_symbol = &root1})});
+        std::make_unique<ReferenceComponentNode>(ReferenceComponent{
+            .identifier = "foo",
+            .ref_type = ReferenceType::kUnqualified,
+            .required_metatype = SymbolMetaType::kUnspecified,
+            .resolved_symbol = &root1})});
     // CheckIntegrity() will fail on destruction of symbol_table_2.
   };
   EXPECT_DEATH(test_func(),
@@ -421,12 +417,11 @@
     // symbol_table1 will outlive symbol_table_2, so give symbol_table_2 a
     // pointer to symbol_table_1.
     root1.Value().local_references_to_bind.push_back(DependentReferences{
-        .components =
-            std::make_unique<ReferenceComponentNode>(ReferenceComponent{
-                .identifier = "foo",
-                .ref_type = ReferenceType::kUnqualified,
-                .required_metatype = SymbolMetaType::kUnspecified,
-                .resolved_symbol = &root1})});
+        std::make_unique<ReferenceComponentNode>(ReferenceComponent{
+            .identifier = "foo",
+            .ref_type = ReferenceType::kUnqualified,
+            .required_metatype = SymbolMetaType::kUnspecified,
+            .resolved_symbol = &root1})});
     root2.Value().declared_type.user_defined_type =
         root1.Value().local_references_to_bind.front().components.get();
     // CheckIntegrity() will fail on destruction of symbol_table_2.
diff --git a/verilog/formatting/align.cc b/verilog/formatting/align.cc
index de82da2..c9294c0 100644
--- a/verilog/formatting/align.cc
+++ b/verilog/formatting/align.cc
@@ -733,6 +733,7 @@
         ReserveNewColumn(node, FlushLeft);
         break;
       }
+      case NodeEnum::kNetDeclarationAssignment:
       case NodeEnum::kNetVariable: {
         // at path [2,0] in kNetDeclaration
         // contains the declared id
@@ -823,6 +824,15 @@
     VLOG(2) << __FUNCTION__ << ", node: " << tag << " at "
             << TreePathFormatter(Path());
     switch (tag) {
+      case NodeEnum::kDeclarationDimensions: {
+        if (current_path_ == SyntaxTreePath{1, 0, 0, 3, 0}) {
+          SyntaxTreePath new_path{1, 0, 0, 3};
+          const ValueSaver<SyntaxTreePath> path_saver(&current_path_, new_path);
+          TreeContextPathVisitor::Visit(node);
+          return;
+        }
+        break;
+      }
       case NodeEnum::kDataDeclaration:
       case NodeEnum::kVariableDeclarationAssignment: {
         // Don't wait for the type node, just start the first column right away.
diff --git a/verilog/formatting/formatter_test.cc b/verilog/formatting/formatter_test.cc
index b0cf132..411e583 100644
--- a/verilog/formatting/formatter_test.cc
+++ b/verilog/formatting/formatter_test.cc
@@ -434,6 +434,10 @@
      "             aaaa     == zzz;\n"
      "             ggg      == vv::w;,\n"
      "             \"Failed to ..........\")\n"},
+    {// macro call nested with function call containing an ifdef
+     "`J(D(`ifdef e))\n",
+     "`J(D(\n"
+     "   `ifdef e))\n"},
 
     // `uvm macros indenting
     {
@@ -3407,6 +3411,20 @@
         "  );\n"
         "endmodule\n",
     },
+    {// Two consecutive EOL comments in kDPIImportItem
+     "import \"DPI-C\" context function void foo(\n"
+     "  input bit first,\n"
+     "  // c3\n"
+     "  // c3+\n"
+     "  input bit second\n"
+     ");\n",
+     "import \"DPI-C\" context\n"
+     "    function void foo(\n"
+     "  input bit first,\n"
+     "  // c3\n"
+     "  // c3+\n"
+     "  input bit second\n"
+     ");\n"},
     {"import \"DPI-C\" context function void func(input bit impl_i,"
      "input bit op_i,"
      "input bit [5:0] mode_i,"
@@ -3426,7 +3444,7 @@
      "  input  bit [7:0]       data_i   [],\n"
      "  output bit [7:0]       data_o   []\n"
      ");\n"},
-    {// module with system task call
+    {// module with system task call w or w/o parentheses
      "module m; initial begin #10 $display(\"foo\"); $display(\"bar\");"
      "end endmodule",
      "module m;\n"
@@ -3435,6 +3453,15 @@
      "    $display(\"bar\");\n"
      "  end\n"
      "endmodule\n"},
+    {// module with system task call
+     "module m; initial begin #10 $display; $display;"
+     "end endmodule",
+     "module m;\n"
+     "  initial begin\n"
+     "    #10 $display;\n"
+     "    $display;\n"
+     "  end\n"
+     "endmodule\n"},
 
     // interface test cases
     {// two interface declarations
@@ -4299,6 +4326,21 @@
      "    endfunction\n"
      "  endclass\n"
      "endpackage\n"},
+    {"package fedex;\n"
+     "  import \"asdf\" context function void bar(\n"
+     "    input bit              [2:1] aaaa,   // EOL COMMENT\n"
+     "                                          // another\n"
+     "    input bit foo\n"
+     "  );\n"
+     "endpackage\n",
+     "package fedex;\n"
+     "  import \"asdf\" context\n"
+     "      function void bar(\n"
+     "    input\n        bit [2:1] aaaa,  // EOL COMMENT\n"
+     "                         // another\n"
+     "    input bit foo\n"
+     "  );\n"
+     "endpackage\n"},
 
     // function test cases
     {"function f ;endfunction", "function f;\nendfunction\n"},
@@ -5475,6 +5517,43 @@
      "  );\n"
      "endmodule\n"},
 
+    {// comment following a delay in next line
+     "module t;\n"
+     "reg x;\n"
+     "initial begin\n"
+     "#20\n"
+     "//comment\n"
+     "x = 1;\n"
+     "x = 2;\n"
+     "end\n"
+     "endmodule\n",
+     "module t;\n"
+     "  reg x;\n"
+     "  initial begin\n"
+     "    #20\n"
+     "    //comment\n"
+     "    x = 1;\n"
+     "    x = 2;\n"
+     "  end\n"
+     "endmodule\n"},
+    {// comment following a delay in the same line
+     "module t;\n"
+     "reg x;\n"
+     "initial begin\n"
+     "#20 //comment\n"
+     "x = 1;\n"
+     "x = 2;\n"
+     "end\n"
+     "endmodule\n",
+     "module t;\n"
+     "  reg x;\n"
+     "  initial begin\n"
+     "    #20  //comment\n"
+     "    x = 1;\n"
+     "    x = 2;\n"
+     "  end\n"
+     "endmodule\n"},
+
     {
         // test that alternate top-syntax mode works
         "// verilog_syntax: parse-as-module-body\n"
@@ -5588,6 +5667,13 @@
      "    .rst  (rst),\n"
      "    .value(value)\n"
      ");\n"},
+    {"bind foo bar baz(\\\n"
+     "`undef d\\\n"
+     "`undef d);",
+     "bind foo bar baz (\\\n"
+     "    `undef d\\\n"
+     "    `undef d\n"
+     ");\n"},
     {
         "bind expaaaaaaaaaaand_meeee looooooooong_name# ("
         ".W(W_CONST), .H(H_CONST), .D(D_CONST)  )"
@@ -8049,6 +8135,19 @@
      "  generic_type_name_t third  // c\n"
      ";\n"
      "} type_t;\n"},
+    {"struct {logic test1; // c\n"
+     "logic test2;} test3;\n",
+     "struct {\n"
+     "  logic test1;  // c\n"
+     "  logic test2;\n"
+     "} test3;\n"},
+    {"struct {\n"
+     "  /* t */ logic test1; /* t */\n"
+     "/* t */ logic test2; }test3;\n",
+     "struct {\n"
+     "  /* t */ logic test1;  /* t */\n"
+     "  /* t */ logic test2;\n"
+     "} test3;\n"},
     // Continuation comment alignment
     {"`define BAR 1 // A\n"
      "module foo(); // B\n"
@@ -15182,6 +15281,60 @@
      ";  // c1\n"
      "   // c2\n"
      "   // c3\n"},
+    {"module indent();\n"
+     "   reg     a;\n"
+     "   reg [32:0] b;\n"
+     "   wire    c;\n"
+     "   wire    d = e ? kFoo : kBar;\n"
+     "endmodule\n",
+     "module indent ();\n"
+     "  reg         a;\n"
+     "  reg  [32:0] b;\n"
+     "  wire        c;\n"
+     "  wire        d = e ? kFoo : kBar;\n"
+     "endmodule\n"},
+
+    {"class C; T1 b; logic [$] a; T1 [$] c; endclass\n",
+     "class C;\n"
+     "  T1        b;\n"
+     "  logic [$] a;\n"
+     "  T1    [$] c;\n"
+     "endclass\n"},
+    {"class C;\n"
+     "  T1 b; //test\n"
+     "  logic [$] a; //test\n"
+     "  T1    [$] c; //test\n"
+     "endclass\n",
+     "class C;\n"
+     "  T1        b;  //test\n"
+     "  logic [$] a;  //test\n"
+     "  T1    [$] c;  //test\n"
+     "endclass\n"},
+    {"class C;\n"
+     "  T1\n"
+     "  b;\n"
+     "  logic\n"
+     "  [$]\n"
+     "  a;\n"
+     "  T1\n"
+     "  [$]\n"
+     "  c;\n"
+     "endclass\n",
+     "class C;\n"
+     "  T1        b;\n"
+     "  logic [$] a;\n"
+     "  T1    [$] c;\n"
+     "endclass\n"},
+    {"class C;\n"
+     "  logic/*t*/ [0 : 1] /*t*/\n"
+     "  a;/*t*/\n"
+     "  T1/*t*/[0 : 1]/*t*/\n"
+     "  c;/*t*/\n"
+     "endclass\n",
+     "class C;\n"
+     "  logic/*t*/ [0 : 1]  /*t*/ a;  /*t*/\n"
+     "  T1/*t*/    [0 : 1]  /*t*/ c;  /*t*/\n"
+     "endclass\n"},
 
     // -----------------------------------------------------------------
 };
diff --git a/verilog/formatting/tree_unwrapper.cc b/verilog/formatting/tree_unwrapper.cc
index c71f9a5..5bd857a 100644
--- a/verilog/formatting/tree_unwrapper.cc
+++ b/verilog/formatting/tree_unwrapper.cc
@@ -469,7 +469,8 @@
   while (!NextUnfilteredToken()->isEOF()) {
     EatSpaces();
     VLOG(4) << "lookahead token: " << VerboseToken(*NextUnfilteredToken());
-    if (IsComment(verilog_tokentype(NextUnfilteredToken()->token_enum()))) {
+    if (auto next_tok = verilog_tokentype(NextUnfilteredToken()->token_enum());
+        IsComment(next_tok) || next_tok == verilog_tokentype::TK_LINE_CONT) {
       // TODO(fangism): or IsAttribute().  Basically, any token that is not
       // in the syntax tree and not a space.
       AdvanceLastVisitedLeaf();
@@ -563,7 +564,7 @@
     case NodeEnum::kPortDeclarationList:
     case NodeEnum::kActualParameterByNameList:
     case NodeEnum::kPortActualList:
-    // case NodeEnum::kPortList:  // TODO(fangism): for task/function ports
+    case NodeEnum::kPortList:
     case NodeEnum::kModuleItemList:
     case NodeEnum::kGenerateItemList:
     case NodeEnum::kClassItems:
@@ -970,9 +971,11 @@
     {
       // Single statements directly inside a flow-control construct
       // should be properly indented one level.
-      const int indent = ShouldIndentRelativeToDirectParent(Context())
-                             ? style_.indentation_spaces
-                             : 0;
+      const int indent = Context().IsInside(NodeEnum::kBlockItemStatementList)
+                             ? 0
+                             : ShouldIndentRelativeToDirectParent(Context())
+                                   ? style_.indentation_spaces
+                                   : 0;
       VisitIndentedSection(node, indent,
                            PartitionPolicyEnum::kFitOnLineElseExpand);
       break;
@@ -2060,8 +2063,9 @@
 
     paren_group_ = FindDirectChild(main_node_,
                                    OriginTagIs{NodeTag(NodeEnum::kParenGroup)});
+
+    // Macro can be used without parentheses
     if (!paren_group_) {
-      LOG_PARTITION_BUG("paren_group not found.");
       return false;
     }
 
@@ -2872,7 +2876,8 @@
       // RHS may have been further partitioned, e.g. a macro call.
       auto& children = partition.Children();
       if (children.size() == 2 &&
-          verible::is_leaf(children.front()) /* left side */) {
+          verible::is_leaf(children.front()) /* left side */ &&
+          !PartitionIsForcedIntoNewLine(children.back())) {
         verible::MergeLeafIntoNextLeaf(&children.front());
         VLOG(4) << "after merge leaf (left-into-right):\n" << partition;
       }
diff --git a/verilog/tools/ls/BUILD b/verilog/tools/ls/BUILD
index fe67b55..0cf92d1 100644
--- a/verilog/tools/ls/BUILD
+++ b/verilog/tools/ls/BUILD
@@ -62,16 +62,29 @@
     ],
 )
 
-cc_binary(
-    name = "verible-verilog-ls",
-    srcs = ["verilog_ls.cc"],
+cc_library(
+    name = "verilog-language-server",
+    srcs = ["verilog-language-server.cc"],
+    hdrs = ["verilog-language-server.h"],
     visibility = ["//visibility:public"],
     deps = [
         ":lsp-parse-buffer",
         ":verible-lsp-adapter",
         "//common/lsp:json-rpc-dispatcher",
         "//common/lsp:lsp-protocol",
+        "//common/lsp:lsp-text-buffer",
         "//common/lsp:message-stream-splitter",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_binary(
+    name = "verible-verilog-ls",
+    srcs = ["verilog_ls.cc"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":verilog-language-server",
         "//common/util:init_command_line",
     ],
 )
diff --git a/verilog/tools/ls/verilog-language-server.cc b/verilog/tools/ls/verilog-language-server.cc
new file mode 100644
index 0000000..8f3ef14
--- /dev/null
+++ b/verilog/tools/ls/verilog-language-server.cc
@@ -0,0 +1,171 @@
+// Copyright 2021-2022 The Verible Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "verilog/tools/ls/verilog-language-server.h"
+
+#include <functional>
+
+#include "absl/strings/string_view.h"
+#include "common/lsp/lsp-protocol.h"
+#include "verilog/tools/ls/verible-lsp-adapter.h"
+
+namespace verilog {
+
+VerilogLanguageServer::VerilogLanguageServer(const WriteFun &write_fun)
+    : dispatcher_(write_fun), buffers_(&dispatcher_) {
+  // All bodies the stream splitter extracts are pushed to the json dispatcher
+  stream_splitter_.SetMessageProcessor(
+      [this](absl::string_view header, absl::string_view body) {
+        return dispatcher_.DispatchMessage(body);
+      });
+
+  // Whenever there is a new parse result ready, use that as an opportunity
+  // to send diagnostics to the client.
+  buffers_.SetChangeListener(parsed_buffers_.GetSubscriptionCallback());
+  parsed_buffers_.SetChangeListener(
+      [this](const std::string &uri,
+             const verilog::BufferTracker &buffer_tracker) {
+        SendDiagnostics(uri, buffer_tracker);
+      });
+  SetRequestHandlers();
+}
+
+void VerilogLanguageServer::SetRequestHandlers() {
+  // Exchange of capabilities.
+  dispatcher_.AddRequestHandler("initialize",
+                                [this](const nlohmann::json &params) {
+                                  return InitializeRequestHandler(params);
+                                });
+
+  dispatcher_.AddRequestHandler(  // Provide diagnostics on request
+      "textDocument/diagnostic",
+      [this](const verible::lsp::DocumentDiagnosticParams &p) {
+        return verilog::GenerateDiagnosticReport(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+
+  dispatcher_.AddRequestHandler(  // Provide autofixes
+      "textDocument/codeAction",
+      [this](const verible::lsp::CodeActionParams &p) {
+        return verilog::GenerateLinterCodeActions(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+
+  dispatcher_.AddRequestHandler(  // Provide document outline/index
+      "textDocument/documentSymbol",
+      [this](const verible::lsp::DocumentSymbolParams &p) {
+        return verilog::CreateDocumentSymbolOutline(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+
+  dispatcher_.AddRequestHandler(  // Highlight related symbols under cursor
+      "textDocument/documentHighlight",
+      [this](const verible::lsp::DocumentHighlightParams &p) {
+        return verilog::CreateHighlightRanges(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+
+  dispatcher_.AddRequestHandler(  // format range of file
+      "textDocument/rangeFormatting",
+      [this](const verible::lsp::DocumentFormattingParams &p) {
+        return verilog::FormatRange(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+  dispatcher_.AddRequestHandler(  // format entire file
+      "textDocument/formatting",
+      [this](const verible::lsp::DocumentFormattingParams &p) {
+        return verilog::FormatRange(
+            parsed_buffers_.FindBufferTrackerOrNull(p.textDocument.uri), p);
+      });
+  // The client sends a request to shut down. Use that to exit our loop.
+  dispatcher_.AddRequestHandler("shutdown", [this](const nlohmann::json &) {
+    shutdown_requested_ = true;
+    return nullptr;
+  });
+}
+
+absl::Status VerilogLanguageServer::Run(const ReadFun &read_fun) {
+  shutdown_requested_ = false;
+  absl::Status status = absl::OkStatus();
+  while (status.ok() && !shutdown_requested_) {
+    status = stream_splitter_.PullFrom(read_fun);
+  }
+  return status;
+}
+
+void VerilogLanguageServer::PrintStatistics() const {
+  if (shutdown_requested_) {
+    std::cerr << "Shutting down due to shutdown request." << std::endl;
+  }
+
+  std::cerr << "Statistics" << std::endl;
+  std::cerr << "Largest message seen: "
+            << stream_splitter_.StatLargestBodySeen() / 1024 << " kiB "
+            << std::endl;
+  for (const auto &stats : dispatcher_.GetStatCounters()) {
+    fprintf(stderr, "%30s %9d\n", stats.first.c_str(), stats.second);
+  }
+}
+
+verible::lsp::InitializeResult VerilogLanguageServer::InitializeRequestHandler(
+    const nlohmann::json &params) const {
+  // Ignore passed client capabilities from params right now,
+  // just announce what we do.
+  verible::lsp::InitializeResult result;
+  result.serverInfo = {
+      .name = "Verible Verilog language server.",
+      .version = GetVersionNumber(),
+  };
+  result.capabilities = {
+      {
+          "textDocumentSync",
+          {
+              {"openClose", true},  // Want open/close events
+              {"change", 2},        // Incremental updates
+          },
+      },
+      {"codeActionProvider", true},               // Autofixes for lint errors
+      {"documentSymbolProvider", true},           // Symbol-outline of file
+      {"documentRangeFormattingProvider", true},  // Format selection
+      {"documentFormattingProvider", true},       // Full file format
+      {"documentHighlightProvider", true},        // Highlight same symbol
+      {"diagnosticProvider",                      // Pull model of diagnostics.
+       {
+           {"interFileDependencies", false},
+           {"workspaceDiagnostics", false},
+       }},
+  };
+
+  return result;
+}
+
+void VerilogLanguageServer::SendDiagnostics(
+    const std::string &uri, const verilog::BufferTracker &buffer_tracker) {
+  // TODO(hzeller): Cache result and rate-limit.
+  // This should not send anything if the diagnostics we're about to
+  // send would be exactly the same as last time.
+  verible::lsp::PublishDiagnosticsParams params;
+
+  // For the diagnostic notification (that we send somewhat unsolicited), we
+  // limit the number of diagnostic messages. In the
+  // textDocument/diagnostic RPC request, we send all of them.
+  // Arbitrary limit here. Maybe set with flag ?
+  static constexpr int kDiagnosticLimit = 500;
+  params.uri = uri;
+  params.diagnostics =
+      verilog::CreateDiagnostics(buffer_tracker, kDiagnosticLimit);
+  dispatcher_.SendNotification("textDocument/publishDiagnostics", params);
+}
+
+};  // namespace verilog
diff --git a/verilog/tools/ls/verilog-language-server.h b/verilog/tools/ls/verilog-language-server.h
new file mode 100644
index 0000000..ed2fdde
--- /dev/null
+++ b/verilog/tools/ls/verilog-language-server.h
@@ -0,0 +1,72 @@
+// Copyright 2021-2022 The Verible Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef VERILOG_TOOLS_LS_LS_WRAPPER_H
+#define VERILOG_TOOLS_LS_LS_WRAPPER_H
+
+#include "absl/status/status.h"
+#include "common/lsp/json-rpc-dispatcher.h"
+#include "common/lsp/lsp-text-buffer.h"
+#include "common/lsp/message-stream-splitter.h"
+#include "verilog/tools/ls/lsp-parse-buffer.h"
+
+namespace verilog {
+
+// Class implementing the Language Server for Verilog
+class VerilogLanguageServer {
+ public:
+  using ReadFun = verible::lsp::MessageStreamSplitter::ReadFun;
+  using WriteFun = verible::lsp::JsonRpcDispatcher::WriteFun;
+
+  static std::string GetVersionNumber() { return "0.0 alpha"; }
+
+  // Constructor preparing the callbacks for Language Server requests
+  VerilogLanguageServer(const WriteFun &write_fun);
+
+  // Runs the Language Server
+  absl::Status Run(const ReadFun &read_fun);
+
+  // Prints statistics of the current Language Server session
+  void PrintStatistics() const;
+
+ private:
+  // Creates callbacks for requests from Language Server Client
+  void SetRequestHandlers();
+
+  // The "initialize" method requests server capabilities.
+  verible::lsp::InitializeResult InitializeRequestHandler(
+      const nlohmann::json &params) const;
+
+  // Publish a diagnostic sent to the server.
+  void SendDiagnostics(const std::string &uri,
+                       const verilog::BufferTracker &buffer_tracker);
+
+  // Stream splitter splits the input stream into messages (header/body).
+  verible::lsp::MessageStreamSplitter stream_splitter_;
+
+  // Parser for JSON messages from LS client
+  verible::lsp::JsonRpcDispatcher dispatcher_;
+
+  // Object for keeping track of updates in opened buffers on client's side
+  verible::lsp::BufferCollection buffers_;
+
+  // Tracks changes in buffers from BufferCollection and parses their contents
+  verilog::BufferTrackerContainer parsed_buffers_;
+
+  // A flag for indicating "shutdown" request
+  bool shutdown_requested_ = false;
+};
+
+};      // namespace verilog
+#endif  // VERILOG_TOOLS_LS_LS_WRAPPER_H
diff --git a/verilog/tools/ls/verilog_ls.cc b/verilog/tools/ls/verilog_ls.cc
index 3656850..b50974f 100644
--- a/verilog/tools/ls/verilog_ls.cc
+++ b/verilog/tools/ls/verilog_ls.cc
@@ -1,4 +1,4 @@
-// Copyright 2021 The Verible Authors.
+// Copyright 2021-2022 The Verible Authors.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -13,17 +13,13 @@
 // limitations under the License.
 //
 
+#include <functional>
 #include <iomanip>  // Only needed for json debugging right now
 #include <iostream>
 
-#include "common/lsp/json-rpc-dispatcher.h"
-#include "common/lsp/lsp-protocol.h"
-#include "common/lsp/message-stream-splitter.h"
 #include "common/util/init_command_line.h"
-#include "verilog/tools/ls/lsp-parse-buffer.h"
-#include "verilog/tools/ls/verible-lsp-adapter.h"
+#include "verilog/tools/ls/verilog-language-server.h"
 
-// Windows specific implementation of read()
 #ifndef _WIN32
 #include <unistd.h>
 #else
@@ -34,187 +30,34 @@
 #define read(fd, buf, size) _read(fd, buf, size)
 #endif
 
-using nlohmann::json;
-using verible::lsp::BufferCollection;
-using verible::lsp::InitializeResult;
-using verible::lsp::JsonRpcDispatcher;
-using verible::lsp::MessageStreamSplitter;
-
-static std::string GetVersionNumber() {
-  return "0.0 alpha";  // TODO(hzeller): once ready, extract from build version
-}
-
-// The "initialize" method requests server capabilities.
-static InitializeResult InitializeServer(const nlohmann::json &params) {
-  // Ignore passed client capabilities from params right now,
-  // just announce what we do.
-  InitializeResult result;
-  result.serverInfo = {
-      .name = "Verible Verilog language server.",
-      .version = GetVersionNumber(),
-  };
-  result.capabilities = {
-      {
-          "textDocumentSync",
-          {
-              {"openClose", true},  // Want open/close events
-              {"change", 2},        // Incremental updates
-          },
-      },
-      {"codeActionProvider", true},               // Autofixes for lint errors
-      {"documentSymbolProvider", true},           // Symbol-outline of file
-      {"documentRangeFormattingProvider", true},  // Format selection
-      {"documentFormattingProvider", true},       // Full file format
-      {"documentHighlightProvider", true},        // Highlight same symbol
-      {"diagnosticProvider",                      // Pull model of diagnostics.
-       {
-           {"interFileDependencies", false},
-           {"workspaceDiagnostics", false},
-       }},
-  };
-
-  return result;
-}
-
-// Publish a diagnostic sent to the server.
-static void SendDiagnostics(const std::string &uri,
-                            const verilog::BufferTracker &buffer_tracker,
-                            JsonRpcDispatcher *dispatcher) {
-  // TODO(hzeller): Cache result and rate-limit.
-  // This should not send anything if the diagnostics we're about to
-  // send would be exactly the same as last time.
-  verible::lsp::PublishDiagnosticsParams params;
-
-  // For the diagnostic notification (that we send somewhat unsolicited), we
-  // limit the number of diagnostic messages. In the
-  // textDocument/diagnostic RPC request, we send all of them.
-  // Arbitrary limit here. Maybe set with flag ?
-  static constexpr int kDiagnosticLimit = 500;
-  params.uri = uri;
-  params.diagnostics =
-      verilog::CreateDiagnostics(buffer_tracker, kDiagnosticLimit);
-  dispatcher->SendNotification("textDocument/publishDiagnostics", params);
+static void FormatHeaderBodyReply(absl::string_view reply) {
+  // Output formatting as header/body chunk as required by LSP spec to stdout.
+  std::cout << "Content-Length: " << reply.size() << "\r\n\r\n";
+  std::cout << reply << std::flush;
 }
 
 int main(int argc, char *argv[]) {
   verible::InitCommandLine(argv[0], &argc, &argv);
 
+  std::cerr << "Verible Alpha Language Server "
+            << verilog::VerilogLanguageServer::GetVersionNumber() << std::endl;
+
 #ifdef _WIN32
+  // Windows messes with newlines by default. Fix this here.
   _setmode(_fileno(stdin), _O_BINARY);
   _setmode(_fileno(stdout), _O_BINARY);
 #endif
 
-  std::cerr << "Verible Alpha Language Server " << GetVersionNumber()
-            << std::endl;
-
   // Input and output is stdin and stdout
-  static constexpr int in_fd = 0;  // STDIN_FILENO
-  JsonRpcDispatcher::WriteFun write_fun = [](absl::string_view reply) {
-    // Output formatting as header/body chunk as required by LSP spec.
-    std::cout << "Content-Length: " << reply.size() << "\r\n\r\n";
-    std::cout << reply << std::flush;
-  };
+  constexpr int kInputFD = 0;  // STDIN_FILENO, but Win does not have that macro
 
-  // Stream splitter splits the input stream into messages (header/body).
-  MessageStreamSplitter stream_splitter;
-  JsonRpcDispatcher dispatcher(write_fun);
+  verilog::VerilogLanguageServer server(FormatHeaderBodyReply);
 
-  // All bodies the stream splitter extracts are pushed to the json dispatcher
-  stream_splitter.SetMessageProcessor(
-      [&dispatcher](absl::string_view /*header*/, absl::string_view body) {
-        return dispatcher.DispatchMessage(body);
-      });
-
-  // The buffer collection keeps track of all the buffers opened in the editor.
-  // It registers callbacks to receive the relevant events on the dispatcher.
-  BufferCollection buffers(&dispatcher);
-
-  // The parsed buffers convert the raw text into the SystemVerilog parsed
-  // representation, basis for all high-level features we provide from it.
-  // Subscribe them to text edit updates to trigger parse.
-  verilog::BufferTrackerContainer parsed_buffers;
-  buffers.SetChangeListener(parsed_buffers.GetSubscriptionCallback());
-
-  // Whenever there is a new parse result ready, use that as an opportunity
-  // to send diagnostics to the client.
-  parsed_buffers.SetChangeListener(
-      [&dispatcher](const std::string &uri,
-                    const verilog::BufferTracker &buffer_tracker) {
-        SendDiagnostics(uri, buffer_tracker, &dispatcher);
-      });
-
-  // -- Register JSON RPC callbacks
-
-  // Exchange of capabilities.
-  dispatcher.AddRequestHandler("initialize", InitializeServer);
-
-  dispatcher.AddRequestHandler(  // Provide diagnostics on request
-      "textDocument/diagnostic",
-      [&parsed_buffers](const verible::lsp::DocumentDiagnosticParams &p) {
-        return verilog::GenerateDiagnosticReport(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-
-  dispatcher.AddRequestHandler(  // Provide autofixes
-      "textDocument/codeAction",
-      [&parsed_buffers](const verible::lsp::CodeActionParams &p) {
-        return verilog::GenerateLinterCodeActions(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-
-  dispatcher.AddRequestHandler(  // Provide document outline/index
-      "textDocument/documentSymbol",
-      [&parsed_buffers](const verible::lsp::DocumentSymbolParams &p) {
-        return verilog::CreateDocumentSymbolOutline(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-
-  dispatcher.AddRequestHandler(  // Highlight related symbols under cursor
-      "textDocument/documentHighlight",
-      [&parsed_buffers](const verible::lsp::DocumentHighlightParams &p) {
-        return verilog::CreateHighlightRanges(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-
-  dispatcher.AddRequestHandler(  // format range of file
-      "textDocument/rangeFormatting",
-      [&parsed_buffers](const verible::lsp::DocumentFormattingParams &p) {
-        return verilog::FormatRange(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-  dispatcher.AddRequestHandler(  // format entire file
-      "textDocument/formatting",
-      [&parsed_buffers](const verible::lsp::DocumentFormattingParams &p) {
-        return verilog::FormatRange(
-            parsed_buffers.FindBufferTrackerOrNull(p.textDocument.uri), p);
-      });
-
-  // The client sends a request to shut down. Use that to exit our loop.
-  bool shutdown_requested = false;
-  dispatcher.AddRequestHandler("shutdown",
-                               [&shutdown_requested](const nlohmann::json &) {
-                                 shutdown_requested = true;
-                                 return nullptr;
-                               });
-
-  absl::Status status = absl::OkStatus();
-  while (status.ok() && !shutdown_requested) {
-    status = stream_splitter.PullFrom([](char *buf, int size) -> int {  //
-      return read(in_fd, buf, size);
-    });
-  }
+  absl::Status status = server.Run([](char *buf, int size) -> int {  //
+    return read(kInputFD, buf, size);
+  });
 
   std::cerr << status.message() << std::endl;
 
-  if (shutdown_requested) {
-    std::cerr << "Shutting down due to shutdown request." << std::endl;
-  }
-
-  std::cerr << "Statistics" << std::endl;
-  std::cerr << "Largest message seen: "
-            << stream_splitter.StatLargestBodySeen() / 1024 << " kiB "
-            << std::endl;
-  for (const auto &stats : dispatcher.GetStatCounters()) {
-    fprintf(stderr, "%30s %9d\n", stats.first.c_str(), stats.second);
-  }
+  server.PrintStatistics();
 }