PAXEL
PAXEL is Pax's expression language.
The Language
PAXEL is very similar to at least two existing languages: Microsoft's Excel spreadsheet formula language, and Google's Common Expression Language (CEL). PAXEL shares the following characteristics with CEL[1]:
memory-safe: programs cannot access unrelated memory, such as out-of-bounds array indexes or use-after-free pointer dereferences;
side-effect-free: a PAXEL program only computes an output from its inputs;
terminating: PAXEL programs cannot loop forever;
strongly-typed: values have a well-defined type, and operators and functions check that their arguments have the expected types;
type-inferred: while explicit type declarations are not required in PAXEL, a type-checking phase occurs before runtime via
rustc, which detects and rejects programs that would violate type constraints.
Why not CEL? Because PAXEL and CEL are so alike, it was tempting to use CEL rather than create a new language. However: PAXEL has a tighter, more specialized scope than CEL and carries a much smaller runtime footprint — namely because CEL is written in Go, which requires embedding Go's runtime and garbage collector, adding megabytes to every WASM / binary file. Further, having complete control of the memory underlying the expression runtime enables the scoping & integration functionality between Pax Properties and Pax Expressions, and the Rust-based machine-code-compiled Expression vtable fits nicely within Pax's "cartridge" model.
PAXEL Grammar
See the source code (opens in a new tab).
Symbolic Identifiers in PAXEL
The following are valid symbolic identifiers in PAXEL:
- properties from the attached Rust struct, such as
self.some_symbolfrom#[pax] pub struct Foo {some_symbol: Property<usize>} - temporaries created by for loops (like
iinfor i in 0..10) - functions exposed as helpers on Pax structs
- certain grammar built-ins like color constants (RED, BLUE, etc.)
If a PAXEL expression refers to self.some_symbol, it will be reactively subscribed to changes in self.some_symbol, updating via the Pax runtime similarly to how a spreadsheet updates.
Helpers
Helpers are functions exposed on a struct and available in PAXEL. See the following example of helpers as exposed by PathElement in pax-std:
#[pax]
#[has_helpers]
pub enum PathElement {
#[default]
Empty,
Point(Size, Size),
Line,
Curve(Size, Size),
Close,
}
#[helpers]
impl PathElement {
pub fn line() -> Self {
Self::Line
}
pub fn close() -> Self {
Self::Close
}
pub fn point(x: Size, y: Size) -> Self {
Self::Point(x, y)
}
pub fn curve(x: Size, y: Size) -> Self {
Self::Curve(x, y)
}
}Example usage of the above in Pax:
<Path class=hr />
@settings {
.hr {
elements: {[
PathElement::point(0%, 0%),
PathElement::line(),
PathElement::point(100%, 0%),
]},
stroke: {
color: rgb(40, 48, 54),
width: 1px,
},
fill: NONE
}
}Shadowing
PAXEL supports shadowed nested symbol declarations, for example you can redeclare i across two nested for loops.
[1] Text modified from source: https://github.com/google/cel-spec/blob/master/doc/langdef.md (opens in a new tab)