Module: lib/core/expression/ops

The runtime primitives a compiled expression calls.

Why these exist separately from the evaluator

Avenx used to evaluate template expressions by walking an AST in the browser. The security boundary lived inside that walk: every property read went through one function with the key already resolved, so x.constructor and x['const'+'ructor'] arrived at the same check as the same string.

Expressions are now compiled to JavaScript at build time, so the walk is gone. The boundary is not: the compiler emits a call to readMember wherever the AST walk would have made one, and to callFunction wherever it would have invoked. The guarantees are therefore identical, and the cost per access drops from a recursive dispatch to one monomorphic call.

This module is what a production bundle keeps. The parser and the evaluator that used to sit above it are build-time only.

What is guaranteed, and what is not

Guaranteed: a compiled expression cannot reach the Function constructor, cannot read or write __proto__ / constructor / prototype however the key is spelled, cannot obtain a built-in prototype object, and cannot name a global outside ALLOWED_GLOBALS. Nothing here uses eval or new Function, and neither does the code the compiler emits, so a page of compiled Avenx expressions needs no 'unsafe-eval'.

Not guaranteed: this is not an isolation boundary against hostile expression source. An expression can still call any function the scope legitimately exposes. The boundary protects the runtime from accidents and from reaching outside the declared scope; it does not make it safe to compile expressions written by an untrusted party.

Source:

Members

(static, constant) ALLOWED_GLOBALS :Set:.<string:>

Globals an expression may name.

Deliberately identical to the set the AST evaluator allowed, so migrating an application changes nothing about which globals its templates can see.

Type:
  • Set:.<string:>
Source:

(static, constant) EXPRESSION_OPS :Object:.<string:, function()>

The primitives a compiled expression calls, keyed by the name the generator emits for each.

One map rather than a list repeated at every consumer. A generated module gets these as named imports the bundler resolves; a host that evaluates a bare class body instead (avenx-core/tooling, the Vite plugin) injects them from here. lib/compiler/codegen/expression.js declares the same names on the emitting side, and a test requires the two to agree.

Type:
  • Object:.<string:, function()>
Source:

(static, constant) FORBIDDEN_KEYS :Set:.<string:>

Property names an expression may never read or write.

Checked against the resolved key, so a computed access spelled x['const' + 'ructor'] is rejected on the same terms as x.constructor.

Type:
  • Set:.<string:>
Source:

(static, constant) RESTRICTED_GLOBALS :Set:.<string:>

Globals an expression may never name, whether or not the host defines them.

Listed explicitly rather than inferred from in globalThis, because the diagnostic has to be the same in every environment: localStorage is restricted in a browser, in happy-dom and in bare Node, and a developer reading AVX_R15 should not get a different answer depending on where the expression happened to run.

Type:
  • Set:.<string:>
Source:

(inner, constant) FUNCTION_CONSTRUCTORS :Set:.<function()>

The dynamic-code constructors.

Reaching any of these would be arbitrary code execution, so they are refused as values however they were obtained.

Type:
  • Set:.<function()>
Source:

(inner, constant) PROTECTED_PROTOTYPES :Set:.<object:>

Built-in prototypes shared by every object in the realm.

Handing one to an expression would let it mutate state shared with the host page, so they are refused as values rather than by enumerating every mutating method on them.

Type:
  • Set:.<object:>
Source:

Methods

(static) callFunction(fn, thisArg, args, description) → {any}

Calls a function on behalf of an expression.

Parameters:
Name Type Description
fn any

The callee.

thisArg any

The receiver.

args Array:.<any:>

The arguments.

description string

How the callee was written, for the error.

Source:
Returns:

The result.

Type
any

(static) construct(ctor, args, description) → {any}

Constructs a value on behalf of an expression.

Parameters:
Name Type Description
ctor any

The constructor.

args Array:.<any:>

The arguments.

description string

How the constructor was written, for the error.

Source:
Returns:

The constructed value.

Type
any

(static) guardKey(key) → {any}

Validates a computed property key before it is used in an object literal.

Parameters:
Name Type Description
key any

The evaluated key.

Source:
Returns:

The key, when it is allowed.

Type
any

(static) guardValue(value) → {any}

Refuses a value that must never reach an expression.

Parameters:
Name Type Description
value any

The value about to be returned.

Source:
Returns:

The value, when it is allowed.

Type
any

(static) hasIn(key, target) → {boolean}

in applied with the right-hand side coerced, matching the interpreter.

Parameters:
Name Type Description
key any

The key to test.

target any

The object to test against.

Source:
Returns:

Whether the key is present.

Type
boolean

(static) readIdentifier(scope, name) → {any}

Resolves a free identifier against the scope, then the allowed globals.

Scope first, so a component's own Date state key wins over the global, and so the reactive scope registers the dependency. A global is resolved through the tracer's substitution point rather than read straight off globalThis, which is what lets a recording log the non-deterministic values an expression observed and a replay hand the same ones back.

Parameters:
Name Type Description
scope object

The evaluation scope.

name string

The identifier.

Source:
Returns:

The bound value, or undefined when nothing binds it.

Type
any

(static) readMember(object, key, optionalopt) → {any}

Reads a property, with the key already resolved.

This is the gate a source-text check cannot provide. Because the key arrives as a value rather than as source, there is no spelling of it that avoids the check.

Parameters:
Name Type Attributes Description
object any

The object to read from.

key any

The resolved property key.

optional boolean <optional>

Whether the access used ?..

Source:
Returns:

The property value.

Type
any

(static) refuse(message)

Raises a sandbox violation.

Parameters:
Name Type Description
message string

What was refused.

Source:
Throws:

Always.

Type
AvenxError

(static) typeofIdentifier(scope, name) → {string}

typeof applied to a free identifier.

typeof maybeUndefined must not throw for an unbound name, which is the whole reason the operator gets used in a template.

Parameters:
Name Type Description
scope object

The evaluation scope.

name string

The identifier.

Source:
Returns:

The type name.

Type
string

(static) writeIdentifier(scope, name, value) → {any}

Assigns to a free identifier.

Parameters:
Name Type Description
scope object

The evaluation scope.

name string

The identifier.

value any

The value to assign.

Source:
Returns:

The assigned value.

Type
any

(static) writeMember(object, key, value) → {any}

Writes a property, with the key already resolved.

Parameters:
Name Type Description
object any

The object to write to.

key any

The resolved property key.

value any

The value to assign.

Source:
Returns:

The assigned value.

Type
any

(inner) isKnownGlobal(name) → {boolean}

Whether a name exists on the host global object.

Parameters:
Name Type Description
name string

The identifier.

Source:
Returns:

True when the host defines it.

Type
boolean