Template operators differ from JavaScript in compatibility mode

Pitfall By design
A .hash page runs in compatibility mode unless the site turns it off. In that mode Docly's older template operators live on top of JavaScript, and four of them reuse JavaScript's characters: ^= and ~= mean "contains", ^! and ~! mean "does not contain". So h ^= x; is not an XOR assignment — it is a text comparison whose result is thrown away, and the line silently does nothing. A bare ^ or ~ is plain XOR and NOT in both modes. Write h = h ^ x, put a space in a ^ !b, or switch the page to plain JavaScript — recommended for every new site and template: "hashjs": { "compatibilityMode": false } in site.json for the site's .hash pages, or Modern syntax on a schema template.
Applies to: HASH filesTemplates

What you'll see

JavaScript that is correct in an API file behaves differently in a .hash page:

#{
  let h = 5381;
  for (let i = 0; i < text.length; i++) {
    h ^= text.charCodeAt(i);   // does nothing in a .hash page
  }
}#

No error, no warning — h just stays 5381. The same code in #/API/ changes h as expected.

Related symptoms in the same family:

  • let y = ~!x; fails to compile with parsing failed of operator '~!'.
  • if (a ^!b) is read as "a does not contain b", not as a ^ (!b).

What's actually happening

.hash pages outside #/API/ run in compatibility mode: plain JavaScript plus Docly's older template syntax. That syntax includes text operators made of two characters:

OperatorIn compatibility modeIn plain JavaScript
a ^= ba contains b (case-sensitive)XOR assignment
a ~= ba contains b (not case-sensitive)syntax error
a ^! ba does not contain b (case-sensitive)a ^ (!b)
a ~! ba does not contain b (not case-sensitive)syntax error
a ^ b, ~aXOR, NOTXOR, NOT

The pair wins whenever the two characters touch. h ^= x; on its own line is therefore a comparison: it evaluates to true or false, nobody uses the value, and h is never assigned. That is why there is no error.

A single ^ or ~ has no template meaning, so it is plain bitwise XOR and NOT in both modes. (Older Docly versions rejected a bare ^ or ~ in a .hash page with Unknown token '^' in expression. If you still see that error, write the XOR as ((a | b) - (a & b)), or move the code to an API file.)

What to do

Write the assignment out. It works the same in both modes:

h = h ^ text.charCodeAt(i);          // not: h ^= ...
h = ((h * 33) ^ text.charCodeAt(i)) >>> 0;   // a typical string hash

Separate characters you mean separately: a ^ !b with a space, and ~(!x) instead of ~!x.

On a new site, avoid the pitfall entirely by starting in the strictest mode — see Start new sites in strict JavaScript mode.

Or turn compatibility mode off, and the page is plain JavaScript, exactly like an API file:

  • For the site's .hash pages: "hashjs": { "compatibilityMode": false } in #/site.json. See Site.json config file.
  • For a template stored in a schema: tick Modern syntax in that template's settings in the schema editor.

Turning it off also removes the bracket conditions (#[Field]#…#[/Field]#), the other text operators ($= and friends), case-insensitive function names and .Length/.Count. Check that the pages compile before switching a live site.

When a .hash page computes something different from the same code in an API file, suspect a two-character operator first. Test the expression on its own in a scratch page — see Use a scratch hash file to test Docly functions.