<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Passing Curiosity: Posts tagged ast</title>
    <link href="https://passingcuriosity.com/tags/ast/ast.xml" rel="self" />
    <link href="https://passingcuriosity.com" />
    <id>https://passingcuriosity.com/tags/ast/ast.xml</id>
    <author>
        <name>Thomas Sutton</name>
        
        <email>me@thomas-sutton.id.au</email>
        
    </author>
    <updated>2009-03-11T00:00:00Z</updated>
    <entry>
    <title>Dynamic tags, fake arguments, and AST mangling in SPIP</title>
    <link href="https://passingcuriosity.com/2009/dynamic-tags-fake-arguments-ast-mangling-in-spip/" />
    <id>https://passingcuriosity.com/2009/dynamic-tags-fake-arguments-ast-mangling-in-spip/</id>
    <published>2009-03-11T00:00:00Z</published>
    <updated>2009-03-11T00:00:00Z</updated>
    <summary type="html"><![CDATA[<p>One occasion in which <a href="/2009/creating-custom-tags-spip-dynamic/">SPIP’s dynamic tag
facilities</a> is a little lacking is
when you need to access details of the AST – perhaps the name of the template
file, perhaps something else – to generate your content. In this post I’ll
describe the obvious (but wrong) approach to passing values into a dynamic tag
and another technique that actually works.</p>
<p>You should know about the <a href="http://www.spip.net/">SPIP</a>, its <a href="/2008/spip-template-languag/">template
language</a>, <a href="/2008/creating-custom-tags-spip-static/">static
tags</a>, <a href="/2009/creating-custom-tags-spip-dynamic/">dynamic
tags</a> and be comfortable with
<a href="http://www.php.net/">PHP</a> before reading this article.</p>
<p>Unlike static tags – which are implemented by a single function – dynamic
tags are comprised of a set of three functions (in a magically named file)
which are called by the SPIP engine as appropriate. This first of these three
functions (identical to the single function of static tags) is called
something like <code>balise_FOO()</code> and is responsible asking SPIP to call the other
two functions: <code>balise_FOO_stat()</code> – which performs whatever static
calculations are required – and <code>balise_FOO_dyn()</code> which performs the dynamic
calculations and display the final content to the user.</p>
<p>An example might make this more clearer. Suppose I’m creating a <code>#HITS</code> tag to
output the number of hits an article has received. My <code>balise_HITS</code> function
arranges for SPIP to call the <code>balise_HITS_*</code> functions like so:</p>
<ol type="1">
<li><p>It calls <code>balise_HITS_stat</code> to determine the static parameters for the tag
(e.g. “id_article=36”).</p></li>
<li><p>It arranges to call <code>balise_HITS_dyn</code>, passing it the static parameters as
determined by <code>balise_HITS_stat</code>.</p></li>
<li><p><code>balise_HITS_dyn</code> uses the static parameters to query the database, etc.
and produces the appropriate output.</p></li>
</ol>
<p>The code to implement the <code>#HITS</code> tag might look like this:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode php"><code class="sourceCode php"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> balise_HITS(<span class="va">$p</span><span class="ot">,</span> <span class="va">$nom</span><span class="op">=</span><span class="st">'HITS'</span>) {</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>    <span class="va">$args</span> <span class="op">=</span> <span class="dt">array</span>(<span class="st">'id_article'</span>)<span class="ot">;</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> calculer_balise_dynamique(<span class="va">$p</span><span class="ot">,</span> <span class="va">$nom</span><span class="ot">,</span> <span class="va">$args</span>)<span class="ot">;</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> balise_HITS_stat(<span class="va">$args</span><span class="ot">,</span> <span class="va">$filters</span>) {</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="dt">array</span>(<span class="va">$args</span>[<span class="dv">0</span>])<span class="ot">;</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> balise_HITS_dyn(<span class="va">$id_article</span>) {</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>    <span class="va">$now</span> <span class="op">=</span> <span class="fu">date</span>(<span class="st">'c'</span>)<span class="ot">;</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="st">&quot;Article </span><span class="va">$id_article</span><span class="st"> has had 1,000,000 hits as of </span><span class="va">$now</span><span class="st">!&quot;</span><span class="ot">;</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>One way this can fall down is if I want to access details of, for example, the
SPIP templates in determining my static parameters. By the time I’m
determining the parameters, I no longer have access to the abstract-syntax
tree node passed to <code>balise_FOO</code>. All I’ve got at this point, is an array of
arguments to the tag and an array of filters applied to the tag.</p>
<h3 id="the-obvious-solution">The obvious solution</h3>
<p>The obvious solution is to add the value to the “<code>$args</code>” array I pass to
<code>calculer_balise_dynamique()</code>. Alas, this will not work as <code>$args</code> is not (in
spite of its usual name) an array of arguments. It is, in fact, an array of
the names of arguments which SPIP is to automatically fetch and pass on the
the <code>balise_*_stat()</code> call. Appending the name of the template file
(<code>"squelettes/foo.html"</code>, for instance) to <code>$args</code> tells SPIP to look for a
variable called <code>squelettes/foo.html</code> in the context the tag is being used and
pass it through to the next function. Needless to say, this doesn’t work.</p>
<h3 id="the-correct-solution">The correct solution</h3>
<p>The correct solution to this problem is to arrange for SPIP to add the values
you want to the <code>$arguments</code> array (or perhaps the <code>$filters</code> array, but this
may not be a good idea). This array contains the values I requested in the
call to <code>calculer_balise_dynamique</code> along with the values of the parameters
passed into the tag in the template. If I can’t use the former route, then
it’ll have to be the second – I’ll need to add a another “fake” parameter to
the AST node before I call <code>calculer_balise_dynamique</code>. (Yes, I agree. This is
a rather odd way to accomplish my goal, but that’s how it goes in SPIP-land).</p>
<p>There are two parts of the SPIP AST that are relevant here: the <code>Champ</code> nodes
that represent tags, and the <code>Texte</code> nodes the represent “strings” (amongst
other things). The <code>$p</code> that is passed to my <code>balise_*</code> functions is an
instance of the <code>Champ</code> class and I’m going to add a new instance of the
<code>Texte</code> class representing my new “fake” parameter. Creating the new object is
pretty simple, just construct it and set it’s <code>type</code> and <code>texte</code> members. The
following example adds a new parameter containing the name of the template
file:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode php"><code class="sourceCode php"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> balise_TEMPLATE(<span class="va">$p</span><span class="ot">,</span> <span class="va">$nom</span><span class="op">=</span><span class="st">'TEMPLATE'</span>) {</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>    <span class="va">$file</span> <span class="op">=</span> <span class="va">$p</span>-&gt;descr[<span class="st">'sourcefile'</span>]<span class="ot">;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>    <span class="co">// Create the new object</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>    <span class="va">$t</span> <span class="op">=</span> <span class="kw">new</span> Texte<span class="ot">;</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>    <span class="va">$t</span>-&gt;type <span class="op">=</span> <span class="st">'texte'</span><span class="ot">;</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>    <span class="va">$t</span>-&gt;texte <span class="op">=</span> <span class="va">$file</span><span class="ot">;</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>    <span class="co">// Make sure that $p-param is an array (of the right dimensions)</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>    <span class="cf">if</span> (<span class="op">!</span> <span class="fu">is_array</span>(<span class="va">$p</span>-&gt;param) ) {</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>        <span class="va">$p</span>-&gt;param <span class="op">=</span> <span class="dt">array</span>(<span class="dt">array</span>(<span class="dv">0</span>=&gt;<span class="kw">NULL</span>))<span class="ot">;</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>    }</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a>    <span class="co">// Append the object to the tag parameters</span></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>    <span class="va">$p</span>-&gt;param[<span class="dv">0</span>][] <span class="op">=</span> <span class="dt">array</span>(<span class="va">$t</span>)<span class="ot">;</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>    <span class="co">// Call SPIP's dynamic tags code</span></span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a>    <span class="va">$args</span> <span class="op">=</span> <span class="dt">array</span>()<span class="ot">;</span></span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> calculer_balise_dynamique(<span class="va">$p</span><span class="ot">,</span> <span class="va">$nom</span><span class="ot">,</span> <span class="va">$args</span>)<span class="ot">;</span></span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>First the previous code gets the name of the template file from the AST node
for the tag being processed. Then it creates a new <code>Texte</code> object with the
filename as its value. It ensures that the <code>$p-&gt;param</code> member of the <code>Champ</code>
object is an array (and yes, it <em>does</em> seem to start with a <code>NULL</code> so that we
can pretend that the arrays are 1-indexed) and then appends the new object to
it. All that’s left is to call <code>calculer_balise_dynamique</code> as usual.</p>
<p>With this done, the value of the new “fake” parameter will be evaluated and
passed to the <code>balise_TEMPLATE_stat()</code> call in the <code>$args</code> array and then (if
I choose) passed on to the <code>balise_TEMPLATE_dyn()</code> call.</p>
<h3 id="conclusion">Conclusion</h3>
<p>This technique still strikes me as a bit odd, but it’s the only way I can see
to implement this effect without introducing global variables. In my opinion,
<code>calculer_balise_dynamique</code> should take an array of argument values as an
optional fourth argument, but needing to do this sort of thing is probably
fairly rare (though I have seen it in the code for one or two built-in tags).
Even if this technique is the “Right Way(TM)” to pass extra values around,
then it really does need a helper function like <code>interprete_argument_balise</code>
instead of mucking around with AST internals in every tag that need it.</p>]]></summary>
</entry>

</feed>
