Two hash expressions cannot be adjacent
What you'll see
A .hash page that looks correct returns HTTP 500 with one of:
Error: Invalid expression, parsing failed.
Error: Unsupported keyword 'if' in expression
Error: Matchende curly bracket ikke funnet!The markup that produced the second one:
<span>#kortdato(n.Dato)##if (n.Kategori) {# · #n.Kategori##}#</span>Nothing is wrong with the expressions themselves. Each one works in isolation. They fail only because they touch.
The reported line number can point at the wrong line. In a measured case the error page highlighted line 3 while the offending construct was on line 4, and in another it highlighted line 1 for a fault on line 2. Read the position as a hint, not a fact.
What's actually happening
## is the escape for a literal hash. In template text it renders as one #:
[##] → [#]That single rule explains the whole failure. When the parser is inside an expression and reaches the closing #, it looks at the next character. If that character is also #, it has found ## — an escaped hash — so it does not close the expression. It keeps scanning.
So #a##b# is never two expressions. It is one expression whose text is a#b, which then fails to parse. And #kortdato(n.Dato)##if (n.Kategori) {# becomes the single expression kortdato(n.Dato)#if (n.Kategori) { — which is why the engine complains about a keyword if appearing where an expression was expected. The message names a symptom several steps downstream of the cause.
Measured on a live site, one construct per request:
| Written | Result |
|---|---|
[##] | [#] — escaped literal hash |
[#A#x] | [Ax] — expression then text |
[x#B#] | [xB] — text then expression |
[#A# #B#] | [A B] — separated by a space |
[#A##B#] | parse error |
[#if (true) {##A# ja#}#] | [A ja] — works |
The last row is the one that surprises people. {##A# contains two adjacent hashes and parses fine, because the first of them closes the #if (…) {# directive. By the time the parser looks for the next token, only one # remains — there is no pair to escape. The rule is about a hash that closes an expression being followed by another hash, not about two hash characters sitting next to each other.
What to do
Put something between them. A space is usually all the markup needs, and it is what the surrounding HTML would have had anyway:
<!-- fails -->
#dato(n.Dato)##n.Kategori#
<!-- fine -->
#dato(n.Dato)# #n.Kategori#
#dato(n.Dato)#<span>#n.Kategori#</span>If you were concatenating, do it in the code block instead. Two expressions jammed together almost always means you wanted one value. Build it once in #{ }#, where it is ordinary JavaScript with no delimiter to trip over, and emit a single expression:
#{
for (var i = 0; i < items.length; i++) {
var it = items[i];
it.Meta = dato(it.Dato) + (it.Kategori ? ' · ' + it.Kategori : '');
}
}#
<span class="meta">#it.Meta#</span>This is also the answer when you find yourself nesting an #if…{# inside a run of expressions to add an optional fragment. The conditional belongs in the block; the template gets one name.
Remember the escape when you want a literal hash. Writing a colour, an anchor or a Docly path in template text needs the double form:
<span>color: ##0a2540</span> → color: #0a2540
<a href="##top">Til toppen</a> → href="#top"When a page 500s after an edit, suspect adjacency before you suspect the expression. The error text points at the swallowed token, and the line number may be off by one or more. Comment out the run, then reintroduce one expression at a time — or copy the construct into a throwaway page and test it alone, which is how the table above was produced. See Use a scratch hash file to test Docly functions.