title: How to fix this algorithm to for create nested sections in an AST
body: <p>I am struggling to create an algorithm, which takes an array of sibling elements (abstract representations of DOM nodes) and returns that array enhanced with nesting. The nesting rule is quite simple: Every heading starts a new section, which spans until the next heading of the same level (or end of the list). Every section can have more nested sections in it, with the same rule.</p>
  <p>Background: I am trying to convert AST from my CMS to a structure presented in <a href="https://css-tricks.com/sticky-table-of-contents-with-scrolling-active-states/" rel="nofollow noreferrer">Chris Coyers post</a> about the interactive sticky table of contents. Currently, i am using only headings to create a similar effect, (e.g. <a href="https://scotch.io/tutorials/build-your-first-deno-app-with-authentication#toc-build-a-real-web-application-with-deno" rel="nofollow noreferrer">scotch.io</a>), but the effect is not so slick.</p>
  <p>I was able to create some nesting, but my brain doesn't enjoy recursive algorithm like this much, and I've got stuck with at least one bug – one part of nesting is padded with a needless array (note the first &quot;heading3&quot; and so on).</p>
  <p>What I've got so far:</p>
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>let list = ["heading1", "1", "1", "heading2", "1-1", "1-1", "heading3", "1-1-1", "heading3", "1-1-2", "1-1-2", "heading2", "1-2", "1-2", "heading1", "heading2", "2-1", "heading2", "2-2", "heading1", "3", "heading1", "4", "4", "4"]
  
  let findFirstHeading = (list, level) =&gt; list.findIndex(el =&gt; new RegExp(`heading${level}`).test(el))
  
  let splitIntoSections = (list, level) =&gt; {
    let startIndex = findFirstHeading(list, level)
    // Note: while this might seem overcomplicated, it is due to prevent bug, when findIndex returns -1, then adding startIndex + 1 will give a faulty value.
    let endIndex =
      findFirstHeading(list.slice(startIndex + 1), level) === -1 ?
      -1 :
      findFirstHeading(list.slice(startIndex + 1), level) + startIndex + 1
          
    let precedingList = list.slice(0, startIndex)
    let restOfList = endIndex &gt; -1 ? list.slice(endIndex) : null
  
    return restOfList ? [...precedingList,
      splitIntoSections(list.slice(startIndex, endIndex), level + 1),
      ...splitIntoSections(restOfList, level)
    ] : [list]
  }
  
  console.log(JSON.stringify(splitIntoSections(list, 1), null, 2))</code></pre>
  </div>
  </div>
  </p>
  <p>Expected result:</p>
  <pre><code>[
    [
      &quot;heading1&quot;,
      &quot;1&quot;,
      &quot;1&quot;,
      [
        &quot;heading2&quot;,
        &quot;1-1&quot;,
        &quot;1-1&quot;,
        [
          &quot;heading3&quot;,
          &quot;1-1-1&quot;
        ],
        [
          &quot;heading3&quot;,
          &quot;1-1-2&quot;,
          &quot;1-1-2&quot;
        ]
      ],
      [
        &quot;heading2&quot;,
        &quot;1-2&quot;,
        &quot;1-2&quot;
      ]
    ],
    [
      &quot;heading1&quot;,
      [
        &quot;heading2&quot;,
        &quot;2-1&quot;
      ],
      [
        &quot;heading2&quot;,
        &quot;2-2&quot;
      ]
    ],
    [
      &quot;heading1&quot;,
      &quot;3&quot;
    ],
    [
      &quot;heading1&quot;,
      &quot;4&quot;,
      &quot;4&quot;,
      &quot;4&quot;
    ]
  ]
  
  </code></pre>
  <p>Any help would be greatly appreciated. Thank you.</p>