<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="107819" data-post-id="107819">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="mjadczak" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/mjadczak/120/3537_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  mjadczak
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I’ll admit that I haven’t thought this through completely, but would it not be possible to implement something like the <code>n:x,x,x,x,x</code> case by first reading <code>n</code>, putting the parsed integer into the parser state (the <code>context</code>), and then simply having a construct which looked something like this: <code>put_0_into_context_as_x() |&gt; repeat(assert_x_less_than_n() |&gt; integer() |&gt; increment_x_in_context())</code>, therefore using dynamic state to fail or succeed a parser dynamically, but not actually generating any new parsing trees at runtime? In general, for more dynamic things, you could also generate a <code>choice</code> some of whose clauses would fail immediately depending on the current <code>context</code>. It seems at least like a lot of common cases could be implemented like this but perhaps I’m missing some limitation.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="107819" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/nimbleparsec-a-simple-and-fast-parser-combinator-for-elixir/12860/93">Post #92</a>
	                </div>
	            </div>
              <div id="likers-container-107819" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="107819"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #92"></div>
  </section>
</div>
    <div class="postbit" id="107820" data-post-id="107820">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="mjadczak" data-post="93" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/mjadczak/48/3537_2.png" class="avatar"> mjadczak:</div>
<blockquote>
<p>but not actually generating any new parsing trees at runtime?</p>
</blockquote>
</aside>
<p>You don’t have to generate new parse trees at runtime. You just have to return a combinator at runtime. I really hope you can do that without generating and compiling an entire parsing tree! My idea (maybe I didn’t explain myself properly) didn’t generate a tree at runtime. It would generate a tree at compile time and compile that into a higher order function which returns a combinator. Or something like that.</p>
<aside class="quote no-group" data-username="mjadczak" data-post="93" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/mjadczak/48/3537_2.png" class="avatar"> mjadczak:</div>
<blockquote>
<p>It seems at least like a lot of common cases could be implemented like this but perhaps I’m missing some limitation.</p>
</blockquote>
</aside>
<p>I’d like to avoid solutions of the form “a lot of common cases”, because if you’re working only on the common cases you might be locking yourself into a place you can’t escape later. I’m a little short on time, but my basic idea is the following:</p>
<ol>
<li>
<p>Write a version of <code>post_traverse</code>, which returns a “full” result tuple, not only a <code>{acc, context}</code> pair. That is, turn <code>post_traverse</code> into something closer to a normal combinator</p>
</li>
<li>
<p>That means it could consume input based on the function passed as an argument (which it can’t do now!)</p>
</li>
<li>
<p>Because the function you pass into <code>post_traverse</code> can be literally anything, you can then implement this functionality as “user-level” code <em>outside</em> of <code>nimble_parsec</code>.</p>
</li>
</ol>
<p>The way you implement that is not very relevant. The most important is thatyou’d have an avenue to explore compilation of context-sensitive parsers without any changes to <code>nimble_parsec</code>’s core. At least that’s the basic idea, I don’t have the time to code it right now. EDIT: I’m actually a little sleep deprived, so just because this <em>seems</em> like a good idea it doesn’t mean it’s actually a good idea.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="107820" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/nimbleparsec-a-simple-and-fast-parser-combinator-for-elixir/12860/94">Post #93</a>
	                </div>
	            </div>
              <div id="likers-container-107820" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="107820"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #93"></div>
  </section>
</div>
    <div class="postbit" id="107822" data-post-id="107822">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josevalim" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/120/1787_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josevalim
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Elixir</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="tmbb" data-post="90" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/t/77aa72/48.png" class="avatar"> tmbb:</div>
<blockquote>
<p>And somehow the returned combinator must be interpreted at runtime by the parser (instead of compiled into something much more efficient)</p>
</blockquote>
</aside>
<p>Yeah… that’s a no-no because it pretty much defeats the point of NimbleParsec. <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
<aside class="quote no-group" data-username="OvermindDL1" data-post="91" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/overminddl1/48/2677_2.png" class="avatar"> OvermindDL1:</div>
<blockquote>
<p>To be honest I find that file pretty long and unreadable, I’m not sure that’ s a good example.</p>
</blockquote>
</aside>
<p>I am pretty sure I said this before but I don’t mind it. But let’s be honest here, your hand-written example is not really shorter than NimbleParsec’s. The NimbleParsec one was written with readability in mind. If we inline NimbleParsec’s code (using a similar style to yours) and run the formatter on both, NimbleParsec is definitely shorter. And if we update NimbleParsec to also use the new <code>eos</code> stuff and what not, then it comes down to this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule SimpleXML do
  import NimbleParsec

  def parse(xml, opts \\ []) do
    opts = Keyword.put(opts, :context, %{tags: []})
    xml(xml, opts)
  end

  tag = ascii_string([?a..?z, ?A..?Z], min: 1)
  text = ascii_string([not: ?&lt;], min: 1)

  opening_tag = ignore(string("&lt;")) |&gt; concat(tag) |&gt; ignore(string("&gt;"))
  closing_tag = ignore(string("&lt;/")) |&gt; concat(tag) |&gt; ignore(string("&gt;"))

  defcombinatorp :node,
                 opening_tag
                 |&gt; post_traverse(:store_tag_in_context)
                 |&gt; repeat(lookahead_not(string("&lt;/")) |&gt; choice([parsec(:node), text]))
                 |&gt; wrap()
                 |&gt; concat(closing_tag)
                 |&gt; post_traverse(:check_close_tag_and_emit_tag)

  defparsec :xml, parsec(:node) |&gt; eos()

  defp store_tag_in_context(_rest, [tag], %{tags: tags} = context, _line, _offset),
    do: {[tag], %{context | tags: [tag | tags]}}

  defp check_close_tag_and_emit_tag(_rest, [tag, [tag | contents]], context, _line, _offset) do
    context = update_in(context.tags, &amp;tl/1)
    text_or_nodes = if match?([_], contents), do: hd(contents), else: contents
    {[{String.to_atom(tag), [], text_or_nodes}], context}
  end

  defp check_close_tag_and_emit_tag(_rest, [opening, [closing | _]], _context, _line, _offset),
    do: {:error, "closing tag #{inspect(closing)} did not match opening tag #{inspect(opening)}"}
end
</code></pre>
<p>Although I wouldn’t personally write in the style above.</p>
<p>But of course, the ExSpirit one is remarkably concise, which is expected as the libs have different goals.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="107822" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/nimbleparsec-a-simple-and-fast-parser-combinator-for-elixir/12860/95">Post #94</a>
	                </div>
	            </div>
              <div id="likers-container-107822" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="107822"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #94"></div>
  </section>
</div>
    <div class="postbit" id="107823" data-post-id="107823">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="95" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>Yeah… that’s a no-no because it pretty much defeats the point of NimbleParsec. <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
</blockquote>
</aside>
<p>Maybe, BUT this is just for the context-sensitive combinators. I believe that this can be made to work without any changes to the “normal” combinator. It makes sense to me that stateful parsing will be less efficient than the highlght optimized things that NimbleParsec does now. That’s the cost of having a more powerful parser.</p>
<p>As long as this doesn’t make the rest of NimbleParsec less efficient, I think it would make sense to include it. But my bet is that you wouldn’t need to. I think I can add a new combinator to NimbleParsec which will allow users to implement context-sensitive parsers without any changes to NimbleParsec.</p>
<p>Also, don’t think of “interpreting the parser” as something very slow and inefficient. You don’t need to interpret the parsing tree at runtime (I hope…). As <a class="mention" href="/u/overminddl1" rel="nofollow">@OvermindDL1</a> said, it might just cost the same as a function call, which is perfectly acceptable.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="107823" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/nimbleparsec-a-simple-and-fast-parser-combinator-for-elixir/12860/96">Post #95</a>
	                </div>
	            </div>
              <div id="likers-container-107823" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="107823"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #95"></div>
  </section>
</div>
    <div class="postbit" id="107825" data-post-id="107825">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josevalim" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/120/1787_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josevalim
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Elixir</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="tmbb" data-post="96" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/t/77aa72/48.png" class="avatar"> tmbb:</div>
<blockquote>
<p>Also, don’t think of “interpreting the parser” as something very slow and inefficient. You don’t need to interpret the parsing tree at runtime (I hope…). As <a class="mention" href="/u/overminddl1" rel="nofollow">@OvermindDL1</a> said, it might just cost the same as a function call, which is perfectly acceptable.</p>
</blockquote>
</aside>
<p>Maybe I am completely missing the approach you have in mind but NimbleParsec emits function clauses. To evaluate them, you would need to either transform those clauses into cases and control the state machine yourself OR write a hand-written evaluator for each combinator.</p>
<p>If we are interested in going in this direction, then I would rather try the approach I mentioned earlier, where we teach each existing combinator how to read things from context, because that would definitely be less work. So you could do: <code>times(..., min: :context_key)</code> and we get the min from the context, which would mix well with the <code>dynamic</code> combinator proposed earlier.</p>
<p>EDIT: You can also always define your custom functions too, that don’t know about nimble_parsec at all, and hook them in with the <code>parsec(:function_name)</code> combinator.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="107825" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/nimbleparsec-a-simple-and-fast-parser-combinator-for-elixir/12860/97">Post #96</a>
	                </div>
	            </div>
              <div id="likers-container-107825" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="107825"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #96"></div>
  </section>
</div>
    <div class="postbit" id="107828" data-post-id="107828">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tmbb" src="/assets/icons/user-9f439610.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tmbb
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="97" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>Maybe I am completely missing the approach you have in mind but NimbleParsec emits function clauses. To evaluate them, you would need to either transform those clauses into cases and control the state machine yourself OR write a hand-written evaluator for each combinator.</p>
</blockquote>
</aside>
<p>I’m aware of that. That’s why you’d have to expand the AST inside a macro so that the macro could compile the AST into something else, instead of function clauses remember that anything given to a macro can be expanded and compiled into something else. Although there might be sole problems with variable contexts, ao maybe this is not a good idea at all…</p>
<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="97" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>If we are interested in going in this direction, then I would rather try the approach I mentioned earlier, where we teach each existing combinator how to read things from context, because that would definitely be less work</p>
</blockquote>
</aside>
<p>You’re probably right. Although I suggest a more “typesafe” approach in which you write something like this:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">times(..., min: from_context(:key))
</code></pre>
<p>And even something like:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">times(..., min: from_context(:key, fn k -&gt; ... end))
</code></pre>
<p>The <code>from_context/2</code> function would just return a tuple which the parsec compiler would know how to handle and compile.<br>
Which transforms the results prior to useing them. This is very similar to what I do in vampyre! In both cases one has to try to optimize the static parts as open as possible and defer the evaluation of the dynamic ones so that they happen at runtime.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="107828" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/nimbleparsec-a-simple-and-fast-parser-combinator-for-elixir/12860/98">Post #97</a>
	                </div>
	            </div>
              <div id="likers-container-107828" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="107828"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #97"></div>
  </section>
</div>
    <div class="postbit" id="107829" data-post-id="107829">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="OvermindDL1" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/OvermindDL1/120/2677_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  OvermindDL1
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="tmbb" data-post="92" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/letter_avatar_proxy/v4/letter/t/77aa72/48.png" class="avatar"> tmbb:</div>
<blockquote>
<p>Yes, I’m perfectly aware of how trivial it is to do this in ExSpirit. After al, I’ve used the context-sensitive features of ExSpirit to write a pretty cool HTML lexer which was capable of highlighting matching opening and closing tags, something which seems impossible with NimbleParsec. The question is how to implement these features in NimbleParsec’s architecture.</p>
</blockquote>
</aside>
<p>Yeah that’s what I was trying to think, couldn’t think of a way without needing to write a second stage function to post-process the parsed data, but that could easily parse ‘too much’ and waste time…</p>
<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="95" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>I am pretty sure I said this before but I don’t mind it. But let’s be honest here, your hand-written example is not really shorter than NimbleParsec’s.</p>
</blockquote>
</aside>
<p>I actually find the pattern matching style extremely readable.  It’s not always the easiest to follow because of how much it jumps around, but it is quite readable, the heads are encoding essentially all of the parsing.  <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="95" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>And if we update NimbleParsec to also use the new <code>eos</code> stuff and what not, then it comes down to this:</p>
</blockquote>
</aside>
<p>Ah that is much more readable, but the <code>check_close_tag_and_emit_tag</code> function still seems very odd, like  it itself is throwing the error instead of the parsing failing to parse.  I’m curious if it returning that error tuple if the parser then would try other alternate paths if they were defined?</p>
<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="95" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>But of course, the ExSpirit one is remarkably concise, which is expected as the libs have different goals.</p>
</blockquote>
</aside>
<p>It would be even more concise in C++ by a significant margin (ExSpirit’s is easily 3 to 4 times the size on average), but then again the backend of Spirit in C++ is exceptionally large (it’s essentially a compiler written in the C++ template meta-language, so that’s a place Elixir can be <em>MUCH</em> more readable on!).  ^.^</p>
<p>I had considered doing compile-time string parsing in ExSpirit at first but I thought I’d try to stick to the AST at least for version 1.</p>
<p>For parser libraries though I’ve always seen the purpose of all of them that I’ve seen is to have a very concise front-end for defining grammar’s since grammar’s tend to be exceptionally hairy overall, so minimizing everything related to the grammar has always been well worth it.  I don’t reach for ExSpirit after all to do parsing of a simple integer list, but rather for parsing complex language definitions, often for shorter things then simple pattern matching is more effect and more readable then needing to bring in another dependency just to do some parsing in 3 lines instead of 15, but when the different is 300 lines compared to 20000 then it is a significant difference.</p>
<aside class="quote group-livebook_core_team quote-modified" data-username="josevalim" data-post="95" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<pre data-code-wrap="elixir"><code class="lang-elixir">closing_tag = ignore(string("&amp;lt;/")) |&amp;gt; concat(tag) |&amp;gt; ignore(string("&amp;gt;"))
</code></pre>
</blockquote>
</aside>
<p>For a stateful parser this <code>closing_tag</code> would need to be able to take in an argument at parsing time and replace the <code>concat(tag)</code> part with a <code>lit(&amp;1)</code> where <code>&amp;1</code> is the passed in argument.  With that simple change in the user-side definition then the great majority of defined functions there then vanish, from <code>store_tag_in_context</code> to all of <code>check_close_tag_and_emit_tag</code> except for it’s tuple building part (and whatever <code>text_or_nodes = if match?([_], contents), do: hd(contents), else: contents</code> is doing, where-ever the list version of contents comes from it seems like the parser should handle returning it in a normalize format with the rest of the children).  That’s a <em>significant</em> amount of user code that no longer needs to exist.</p>
<blockquote>
<p>It makes sense to me that stateful parsing will be less efficient than the highlght optimized things that NimbleParsec does now. That’s the cost of having a more powerful parser.</p>
</blockquote>
<p>No reason for it to not be except for just taking the time to build the ‘compiler’ to convert from the DSEL to a set of functions to handle it directly (instead of the macro-munging that ExSpirit does currently, I.E. what Parsex was going to do).</p>
<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="97" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>Maybe I am completely missing the approach you have in mind but NimbleParsec emits function clauses. To evaluate them, you would need to either transform those clauses into cases and control the state machine yourself OR write a hand-written evaluator for each combinator.</p>
</blockquote>
</aside>
<p>What C++'s Spirit ends up doing is taking the DSEL used in the type system (in Elixir that’s just grabbing the AST at compile-time), using templates to walk the DSEL’s AST to generate a couple of sets of data of lifetime information for the ‘arguments’ used, holding the types, normalizing the return types, etc… etc… (in Elixir that’s just walking the AST at compile-time to generate data for making the functions), then it runs optimization passes basically a pattern matcher over the processed AST and values to find patterns to reduce to more efficient patterns), then lastly the templates build a single inlineable function per rule that passes the data via direct argument passing (in Elixir that’s basically what NimbleParsec is doing but with the added information of arguments and more stuff to generate specific code for each type of thing), and this goes right down to even something as simple as parsing characters where the <code>char_</code> parser gets any part of it replaced with specific values (say matching “a-z”) and any variables are injected in-place, the C++ compiler then does it’s normal optimization of folding constants (since the template ‘processor’ injects constants in every place it can), which let’s it end up having a call like <code>parse(input.begin(), input.end(), char_ &gt;&gt; int_)</code> take literally a few dozen assembly instructions all inline (if the <code>input</code> type is held in order in memory, otherwise it generates streaming calls and so forth, which is more, but still efficient).</p>
<p>This same pattern is entirely doable in Elixir (and was planned in ExSpirit, I just wanted something workable at first for my own work), it just needs to be done.  <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
<p>As a side note, those ‘optimization passes’ mentioned above is why C++'s Spirit is and will always remain the fastest parser, because anything any other parser does better they can see how it generates the output assembly and make a new optimization pass to optimize for that pattern to at least meet if not exceed the original.  This is all while it is possible for users to create their own (even root terminal) constructs (as a bit of a template horror but thankfully only ever needs to be done once).  It’s entirely doable in Elixir much more simply.  <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="107829" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/nimbleparsec-a-simple-and-fast-parser-combinator-for-elixir/12860/99">Post #98</a>
	                </div>
	            </div>
              <div id="likers-container-107829" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="107829"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #98"></div>
  </section>
</div>
    <div class="postbit" id="107830" data-post-id="107830">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josevalim" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/120/1787_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josevalim
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Elixir</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote no-group" data-username="OvermindDL1" data-post="99" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/overminddl1/48/2677_2.png" class="avatar"> OvermindDL1:</div>
<blockquote>
<p>I actually find the pattern matching style extremely readable. It’s not always the easiest to follow because of how much it jumps around, but it is quite readable, the heads are encoding essentially all of the parsing. <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"></p>
</blockquote>
</aside>
<p>Which is why I didn’t enter the merit of readability (it is personal) and focused on code size. <img src="https://forum.elixirforum.com/images/emoji/apple/slight_smile.png?v=15" title=":slight_smile:" class="emoji" alt=":slight_smile:" loading="lazy" width="20" height="20"> And I just noticed we actually don’t use the tags stored in the context, so now the whole thing can be written as:</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">defmodule SimpleXML do
  import NimbleParsec

  defparsec :parse, parsec(:node) |&gt; eos()

  tag = ascii_string([?a..?z, ?A..?Z], min: 1)
  text = ascii_string([not: ?&lt;], min: 1)

  opening_tag = ignore(string("&lt;")) |&gt; concat(tag) |&gt; ignore(string("&gt;"))
  closing_tag = ignore(string("&lt;/")) |&gt; concat(tag) |&gt; ignore(string("&gt;"))

  defcombinatorp :node,
                 opening_tag
                 |&gt; repeat(lookahead_not(string("&lt;/")) |&gt; choice([parsec(:node), text]))
                 |&gt; wrap()
                 |&gt; concat(closing_tag)
                 |&gt; post_traverse(:match_and_emit_tag)

  defp match_and_emit_tag(_rest, [tag, [tag, text]], context, _line, _offset),
    do: {[{String.to_atom(tag), [], text}], context}

  defp match_and_emit_tag(_rest, [tag, [tag | nodes]], context, _line, _offset),
    do: {[{String.to_atom(tag), [], nodes}], context}

  defp match_and_emit_tag(_rest, [opening, [closing | _]], _context, _line, _offset),
    do: {:error, "closing tag #{inspect(closing)} did not match opening tag #{inspect(opening)}"}
end
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="107830" data-batch-url="/posts/batch_likers">
                        3
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/nimbleparsec-a-simple-and-fast-parser-combinator-for-elixir/12860/100">Post #99</a>
	                </div>
	            </div>
              <div id="likers-container-107830" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="107830"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #99"></div>
  </section>
</div>
    <div class="postbit" id="107890" data-post-id="107890">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="OvermindDL1" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/OvermindDL1/120/2677_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  OvermindDL1
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<aside class="quote group-livebook_core_team" data-username="josevalim" data-post="100" data-topic="12860">
<div class="title">
<div class="quote-controls"></div>
<img alt="" width="24" height="24" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/48/1787_2.png" class="avatar"> josevalim:</div>
<blockquote>
<p>And I just noticed we actually don’t use the tags stored in the context, so now the whole thing can be written as:</p>
</blockquote>
</aside>
<p>Ah much nicer!</p>
<p>I’m curious, I’m guessing line is the line number, what is offset, is it the column number or is the position in the overall file?  Both values are useful for various purposes.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="107890" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/nimbleparsec-a-simple-and-fast-parser-combinator-for-elixir/12860/101">Post #100</a>
	                </div>
	            </div>
              <div id="likers-container-107890" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="107890"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #100"></div>
  </section>
</div>
    <div class="postbit" id="107902" data-post-id="107902">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="josevalim" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/josevalim/120/1787_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  josevalim
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Creator of Elixir</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Offset is the byte offset the line starts. You can compute the column from the line offset and the overall offset.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="107902" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/nimbleparsec-a-simple-and-fast-parser-combinator-for-elixir/12860/102">Post #101</a>
	                </div>
	            </div>
              <div id="likers-container-107902" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="107902"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #101"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <a class="load-more-button" data-turbo-stream="true" href="/topics/12860/load_more?page=11">Load more posts (41 remaining)</a>
</div></template></turbo-stream>