ActionLanguage IR

The intermediate representation (IR) Paradise builds for JavaScript. A tree of actions describing what the program does, not the syntax of how it was written. Two semantically-equivalent JavaScript fragments collapse to the same ActionLanguage tree; accessibility analysis becomes a question about behaviour, not about spelling.

Why a tree of actions, not an AST

An abstract syntax tree captures syntactic structure — the shape of what was typed. A JavaScript AST for button.addEventListener("click", fn) is an ExpressionStatement wrapping a CallExpression on a MemberExpressionwith two arguments. That shape is correct, but it’s not what the accessibility question is about. The question is about the behaviour: is there a click handler on this element, and is it also reachable by keyboard?

The ActionLanguage tree captures behaviour directly. The same call becomes a register-handleraction whose attributes name the target selector, the event type, and the body of effects the handler performs. That representation answers the accessibility question in one hop — the analyser doesn’t have to recover the meaning of the AST shape; the meaning is already there.

The form descends directly from work I started in 2010 on adaptive user interfaces — see Lineage. The PhD-era Action Language Model was designed to describe algorithm fragments precisely enough that they could be substituted at runtime. The same precision is what makes accessibility behaviour analysable.

The model in seven entities

The model has a small, deliberate vocabulary. Every ActionLanguage tree decomposes into instances of these:

Action
A node in the tree. The basic executable unit. Every action has a type and zero or more typed attributes; some have ordered child actions.
ActionType
The kind of action — seq, if, for, register-handler, mutate-style, and so on. Defines which attribute types the action carries and whether it can contain children.
ActionAttribute
A typed attribute on an action — a variable name on an assign, the literal value on a literal, the selector string on a register-handler.
AttributeType
The kind of attribute — var.name, literal.string, selector, event.name. Defines the data type the attribute carries.
AttributeDataType
The data type — String, Integer, Boolean, Selector. Two attributes of different data types can’t be confused for each other in analysis.
SequencedAction
The relationship that holds one action as a child of another in a specific position. Sequencing matters — a; b is not the same as b; a for many accessibility patterns (focus management ordering, ARIA-state updates).
ActionColoring
The link between an action and its type — “this node is of type register-handler”. Separating coloring from the action itself made the PhD-era execution engine easier to reason about; it carries over here mostly out of fidelity to the original model.

Worked example

A small handler that opens a modal and traps focus. The JavaScript that’s typed:

// JavaScript source
openBtn.addEventListener("click", () => {
  modal.style.display = "block";
  trapFocus(modal);
});

The JavaScript AST for that fragment is the syntactic shape — call expressions, arrow functions, member expressions. It says what was written:

// JavaScript AST (simplified)
ExpressionStatement
  CallExpression
    callee: MemberExpression
      object: Identifier("openBtn")
      property: Identifier("addEventListener")
    arguments:
      Literal("click")
      ArrowFunctionExpression
        body: BlockStatement
          ExpressionStatement
            AssignmentExpression
              left: MemberExpression
                object: MemberExpression
                  object: Identifier("modal")
                  property: Identifier("style")
                property: Identifier("display")
              right: Literal("block")
          ExpressionStatement
            CallExpression
              callee: Identifier("trapFocus")
              arguments: [Identifier("modal")]

The ActionLanguage tree for the same fragment says what it does:

// ActionLanguage tree (simplified)
register-handler
  target:    selector(#openBtn)
  event:     click
  effects:
    mutate-style
      target:   selector(#modal)
      property: display
      value:    "block"
    call
      function: trapFocus
      args:     [selector(#modal)]

Three things have happened in the translation. First, the syntactic noise of addEventListener as a method call has collapsed into the action type register-handler — one node that names the behaviour directly. Second, the arrow-function body has been broken out into the effects that occur when the handler fires, with each effect typed by what kind of side-effect it has — a style mutation here, a function call there. Third, the targets have been resolved to selectors — #openBtn for the registration site, #modal for the style mutation — so the DocumentModel can match them against elements in the DOMModel and CSSModel.

What the IR adds

  • Behavioural typing. Every action is one of a small set of types named by the effect it has — register-handler, mutate-style, mutate-attribute, set-focus, conditional, iteration. Querying for “every place that moves focus” is one tree-walk; doing the same on a JavaScript AST means pattern-matching across many syntactic forms.
  • Selector resolution.Targets in the tree are CSS selectors, not opaque strings. The IR doesn’t care whether the source code wrote document.getElementById("x") or document.querySelector("#x") or elementsById.x — they all resolve to the same selector, and the same element in the DOMModel.
  • Cross-handler reasoning. Two handlers registered on the same selector — say a click handler and a keydown handler — produce two register-handler nodes with the same target. An analyser can compare the effects of both nodes to ask whether the keyboard path is equivalent to the pointer path.
  • Source provenance. Every node carries the source file and line range it came from, so analyser diagnostics can point back to the exact lines the developer wrote. The IR is abstract about semantics, not about location.

What the IR discards

  • Syntactic spelling. function f() { return 1; } and const f = () => 1;collapse to the same tree. The accessibility analyser doesn’t care which form the developer chose.
  • Comments and whitespace.The IR doesn’t carry presentation; the source location preserves where the original was, which is what diagnostics need.
  • Optimisation-friendly transforms.The IR is the source author’s intent, not the engine runner’s. Constant folding, dead-code elimination, inlining — none of those happen in the IR. Paradise analyses what was written, not what a JIT would run.

The Adaptation Model

In the PhD-era work the Action Language Model had a twin: the Adaptation Model, which described variations between algorithm versions as add, modify, and delete operations on action nodes. The original use case was substituting algorithm fragments at runtime to suit a particular user.

The Adaptation Model isn’t used in current Paradise. It’s on the roadmap because it answers a different kind of accessibility question: “what is the minimum modification that would make this code accessible for a particular user’s capability profile?”. That direction belongs to the post-2029 framework I plan to take up — see Lineage for the longer story.

Reading on