Skip to main content

The Lux Language Specification

Version 0.1.0 β€” March 2026 Author: David J. Charlot, PhD β€” Open Interface Engineering, Inc. Status: Draft


1. Introduction

Lux is a web programming language that unifies structure, style, logic, and data into a single syntax. It replaces HTML, CSS, JavaScript, TypeScript, and the DOM API with one language that compiles to native code and WebAssembly.

Energy is a first-class observable in Lux: programs produce an energy receipt alongside their output. Aggregate units of work (renders, routes, fetches, state updates) are measured from real hardware power sensors where the host provides them, and costed from a calibrated per-platform model where it does not β€” each figure carrying its measurement source. (See the whitepaper, Β§5, for the measurement stack and its resolution limits.)

Lux source files use the .lux extension.

1.1 Design Principles

  1. One language β€” No separation of markup, style, and logic. A .lux file is a complete program.
  2. Energy-first β€” Every expression, render cycle, and I/O operation is metered in joules.
  3. Sequential clarity β€” Familiar imperative syntax. No new paradigm to learn.
  4. Type-safe by default β€” Gradual typing with full inference. Add annotations when you want, not because you must.
  5. No null β€” Option types (T?) replace null. The billion-dollar mistake does not exist in Lux.
  6. AI-friendly β€” Small grammar, no ambiguity, deterministic parsing. AI code generators produce correct Lux on the first attempt.
  7. Zero-dependency standard library β€” 1,723 modules ship with the runtime. No package manager needed for common tasks.

1.2 Targets

  • Browser: Compiles to WebAssembly (< 400 KB)
  • Server: Compiles to native binary via Cranelift
  • Edge: Compiles to minimal WASM or native for constrained devices
  • Desktop: Native binary with platform windowing

2. Lexical Structure

2.1 Character Set

Lux source files are UTF-8 encoded. Identifiers support Unicode letters and digits.

2.2 Comments

# This is a line comment

#* This is a
   block comment *#

2.3 Indentation

Lux uses indentation to delimit blocks. The lexer emits INDENT and DEDENT tokens. The reference indentation unit is 2 spaces. Tabs are not permitted.

A colon (:) at the end of a line opens a new indentation block. The next line must be indented further than the current level.

if x > 0:
  print("positive")
  print("and non-zero")

2.4 Tokens

2.4.1 Keywords

app      server   view     style    use      effect
let      if       else     for      in       while
match    return   break    continue fn       and
or       not      true     false    none     signal
memo     batch    get      post     put      delete
patch    import   from     as       is       with
yield    await    spawn    try      catch    throw

2.4.2 Operators

OperatorMeaning
+ - * / %Arithmetic
== != < <= > >=Comparison
and or notLogical
=Assignment
->Arrow (lambdas, event handlers)
.Field access
|>Pipe (left-to-right function composition)
?Optional chaining / type optionality
..Range
++String/list concatenation

2.4.3 Delimiters

( )    # grouping, function calls
[ ]    # list literals, indexing
{ }    # record literals, string interpolation
:      # block opener, key-value separator
,      # separator in lists, records, parameters

2.4.4 Literals

42                    # int
3.14                  # float
"hello"               # string
"hello, {name}"       # interpolated string
true / false          # bool
none                  # none value
[1, 2, 3]            # list
{name: "David"}      # record

2.5 Identifiers

Identifiers begin with a Unicode letter or underscore, followed by letters, digits, or underscores. Identifiers are case-sensitive.

x
user_name
_private
Counter
rΓ©sumΓ©
ζ—₯本θͺž

2.6 String Interpolation

Strings delimited by double quotes support interpolation with {expr}:

name = "world"
greeting = "hello, {name}"          # "hello, world"
math = "2 + 2 = {2 + 2}"           # "2 + 2 = 4"
nested = "{user.name} ({user.age})" # "David (40)"

Curly braces in strings are escaped with \{ and \}.

Triple-quoted strings preserve whitespace and support multi-line content:

html = """
  <div>
    <p>preserved indentation</p>
  </div>
"""

3. Type System

3.1 Base Types

TypeDescriptionExample
int64-bit signed integer42
float64-bit IEEE 7543.14
strUTF-8 string"hello"
boolBooleantrue, false
noneAbsence of valuenone

3.2 Compound Types

TypeSyntaxExample
List[T][1, 2, 3]
Record{field: T, ...}{name: "David", age: 40}
Tuple(T, U, ...)(1, "hello")
OptionT?str? β€” string or none
Functionfn(T, U) -> Vfn(int) -> str

3.3 Type Inference

All types are inferred by default. Annotations are optional:

# Inferred
x = 42                    # int
name = "David"            # str
items = [1, 2, 3]         # [int]
user = {name: "David"}    # {name: str}

# Annotated
x: int = 42
name: str = "David"
items: [int] = [1, 2, 3]

3.4 Option Types (No Null)

There is no null in Lux. A value that may be absent has type T?:

find_user(id: int) -> {name: str}?:
  if id == 0:
    none
  else:
    {name: "David"}

user = find_user(1)
print(user?.name)        # optional chaining β€” prints name or does nothing
print(user ?? "default") # coalescing β€” provides fallback

3.5 User-Defined Types

type Point:
  x: float
  y: float

type Shape:
  Circle(radius: float)
  Rect(width: float, height: float)
  Triangle(a: Point, b: Point, c: Point)

type Result[T, E]:
  Ok(value: T)
  Err(error: E)

3.6 Type Conversions

Explicit only. No implicit coercion between numeric types:

x: int = 42
y: float = x.to_float()    # explicit
z: str = x.to_str()        # explicit

4. Expressions

4.1 Precedence (highest to lowest)

LevelOperatorsAssociativity
1. [i] (args)Left
2-x not xRight (unary)
3* / %Left
4+ - ++Left
5|>Left
6..None
7== != < <= > >= isLeft
8andLeft
9orLeft
10->Right

4.2 Everything is an Expression

if, match, and blocks return values:

status = if active: "on" else: "off"

label = match kind:
  "a" -> "Alpha"
  "b" -> "Beta"
  _ -> "Unknown"

result = do:
  x = compute()
  x * 2

4.3 Pipe Operator

Left-to-right function composition:

result = data
  |> filter(item -> item.active)
  |> map(item -> item.name)
  |> sort()
  |> take(10)

4.4 Range Expressions

1..10          # exclusive: 1, 2, ..., 9
1..=10         # inclusive: 1, 2, ..., 10

5. Statements

5.1 Let Bindings

let name = "David"          # immutable (default)
let mut count = 0           # mutable
let x: int = 42             # annotated

The let keyword is optional for top-level and block-level bindings:

name = "David"              # equivalent to let name = "David"
mut count = 0               # equivalent to let mut count = 0

5.2 Assignment

Only mut bindings can be reassigned:

mut count = 0
count = count + 1           # ok

name = "David"
name = "other"              # error: name is immutable

5.3 If / Else

if temperature > 100:
  print("hot")
else if temperature > 50:
  print("warm")
else:
  print("cold")

5.4 For Loops

for item in items:
  print(item)

for item, index in items:
  print("{index}: {item}")

for i in 0..10:
  print(i)

5.5 While Loops

while count < 10:
  count = count + 1

5.6 Match

match shape:
  Circle(r) -> pi * r * r
  Rect(w, h) -> w * h
  _ -> 0.0

5.7 Return / Break / Continue

fn find(items, target):
  for item in items:
    if item == target:
      return item
  none

6. Functions

6.1 Function Definitions

# Multi-line
fn greet(name: str) -> str:
  "Hello, {name}!"

# Single-expression (= syntax)
fn double(x) = x * 2

# Default parameters
fn connect(host: str, port: int = 8080):
  # ...

6.2 Lambda Expressions

fn square = x -> x * x
items.filter(item -> item.active)
items.sort_by((a, b) -> a.name < b.name)

6.3 Closures

Lambdas capture variables from their enclosing scope:

fn make_counter():
  mut count = 0
  increment = () -> count = count + 1; count
  increment

6.4 Positional Arguments and Default Parameters

Calls are positional. Parameters may declare defaults, which apply when an argument is omitted from the tail of the call.

fn create_user(name: str, age: int, active: bool = true):
  {name, age, active}

create_user("David", 40)          # active defaults to true
create_user("David", 40, false)   # active given explicitly

Named/keyword call arguments (create_user(name: "David", age: 40)) are not part of the current grammar. Arguments bind by position.


7. Reactive State

Lux provides reactive primitives as first-class language features, not library imports.

7.1 Signals

A signal is a reactive value that notifies dependents when it changes:

count = signal(0)           # create signal with initial value 0
print(count)                # read: 0
count.set(5)                # write: dependents notified
count.update(n -> n + 1)    # update: read + transform + write

7.2 Memo (Derived State)

A memo is a computed value that automatically re-evaluates when its dependencies change:

count = signal(0)
doubled = memo -> count * 2

count.set(5)
print(doubled)              # 10 β€” automatically recomputed

7.3 Effect (Side Effects)

An effect runs whenever its reactive dependencies change:

count = signal(0)

effect:
  print("Count is now {count}")

count.set(1)                # prints: "Count is now 1"
count.set(2)                # prints: "Count is now 2"

7.4 Batch

Multiple signal updates can be batched into a single notification cycle:

batch:
  first_name.set("David")
  last_name.set("Charlot")
  # dependents notified once, not twice

8. View System

The view system replaces HTML + CSS + DOM manipulation with a declarative component model.

8.1 Elements

Elements are created by name, followed by positional arguments, named properties, and an optional child block:

text "Hello, World"
text "Count: {count}" size=24 color=blue
button "Click me" on:click -> handle_click()

8.2 Layout Elements

column gap=16 padding=24:
  text "Title" size=32 weight=bold
  row gap=8:
    button "Save" on:click -> save()
    button "Cancel" on:click -> cancel()

8.3 Built-in Elements

ElementPurposeExample
textDisplay texttext "hello" size=16
columnVertical flex layoutcolumn gap=8:
rowHorizontal flex layoutrow gap=8:
boxGeneric containerbox padding=16:
buttonClickable buttonbutton "Submit" on:click -> ...
inputText inputinput bind=name placeholder="Name"
checkboxBoolean inputcheckbox bind=active
selectDropdownselect bind=choice options=items
imageImage displayimage src=url alt="photo"
linkNavigation linklink "/about" text="About"
listScrollable listlist items=data:
gridCSS Grid layoutgrid cols=3 gap=8:
canvas2D drawing surfacecanvas width=800 height=600

8.4 Event Handling

Events use the on:event -> handler syntax:

button "Click" on:click -> count.update(n -> n + 1)

input bind=query on:change -> search(query)

box on:hover -> show_tooltip()
    on:leave -> hide_tooltip()

8.5 Two-Way Binding

The bind property creates a two-way connection between an input and a signal:

name = signal("")
input bind=name placeholder="Enter name"
# Typing in the input updates the signal
# Changing the signal updates the input

8.6 Conditional Rendering

view:
  if logged_in:
    text "Welcome, {user.name}"
  else:
    button "Log in" on:click -> login()

8.7 List Rendering

view:
  for todo, i in todos:
    row gap=8 key=i:
      checkbox bind=todo.done
      text todo.text strike=todo.done

The key property is required for list items to enable efficient diffing.

8.8 Style Properties

Style properties are set directly on elements as named properties:

text "Title"
  size=32
  weight=bold
  color="#1a1a1a"
  font="Inter"

box
  padding=24
  background="#ffffff"
  border_radius=8
  shadow="0 2px 8px rgba(0,0,0,0.1)"
  max_width=600

8.9 Named Styles

style card:
  padding = 16
  background = "#ffffff"
  border_radius = 8
  shadow = "0 2px 4px rgba(0,0,0,0.1)"

style card_hover:
  shadow = "0 4px 12px rgba(0,0,0,0.15)"

view:
  box style=card on:hover -> style=card_hover:
    text "Card content"

8.10 Responsive Styles

style container:
  padding = 24
  max_width = 1200

  @width < 768:
    padding = 12
    max_width = "100%"

  @width < 480:
    padding = 8

9. Components

9.1 App Components

An app defines a root component:

app Counter:
  count = signal(0)

  view:
    column gap=16:
      text "Count: {count}" size=24
      button "+" on:click -> count.update(n -> n + 1)

9.2 Reusable Components

Components are functions that return views:

fn Card(title: str, children):
  box style=card:
    text title size=18 weight=bold
    column gap=8:
      children

# Usage
view:
  Card "My Title":
    text "Content inside the card"
    button "Action" on:click -> do_something()

9.3 Component State

Each component instance has its own reactive scope:

fn TodoItem(todo):
  editing = signal(false)

  view:
    if editing:
      input bind=todo.text on:blur -> editing.set(false)
    else:
      text todo.text on:dblclick -> editing.set(true)

9.4 Component Composition

fn App():
  todos = signal([])

  view:
    column:
      Header()
      for todo in todos:
        TodoItem(todo)
      Footer(count: todos.filter(t -> not t.done).len())

10. Server

Lux includes server-side syntax for HTTP APIs.

10.1 Server Definition

server Api port=8080:
  db = connect("jouledb://localhost:5432")

  get "/":
    json({status: "ok", energy: energy.total()})

  get "/users":
    users = db.query("SELECT * FROM users")
    json(users)

  get "/users/{id}":
    user = db.query("SELECT * FROM users WHERE id = ?", id)
    if user:
      json(user)
    else:
      status(404)
      json({error: "not found"})

  post "/users":
    user = body()
    db.query("INSERT INTO users (name, email) VALUES (?, ?)", user.name, user.email)
    status(201)
    json({status: "created"})

10.2 Middleware

server Api port=8080:
  use cors(origins: ["*"])
  use logger()
  use energy_receipt()      # adds X-Energy-Joules header

  get "/":
    json({status: "ok"})

10.3 Request Context

Inside a route handler, the following are available:

NameTypeDescription
body()recordParsed request body (JSON)
paramsrecordURL path parameters
queryrecordURL query parameters
headersrecordRequest headers
methodstrHTTP method
pathstrRequest path

10.4 WebSocket

server Realtime port=8081:
  ws "/chat":
    on:connect -> print("client connected")
    on:message(msg) -> broadcast(msg)
    on:close -> print("client disconnected")

11. Module System

11.1 Files are Modules

Every .lux file is a module. The file name (without extension) is the module name.

# file: utils.lux
fn format_date(timestamp: int) -> str:
  # ...

fn clamp(value, min, max):
  if value < min: min
  else if value > max: max
  else: value

11.2 Use Statements

use ./utils                          # local file (relative path)
use ./components/header              # nested local file
use lux:chart                        # standard library module
use lux:crypto/aes                   # standard library submodule
use ./utils { format_date, clamp }   # selective import
use lux:chart as viz                 # aliased import

11.3 Standard Library

Lux ships with 1,723 modules organized by domain:

DomainExamplesCount
Core Webvdom, reactive, component, router, forms, css30
UI Elementsanimation, drag, gesture, toast, carousel, tabs40
Graphicscanvas2d, webgl, scene3d, mesh, lighting, particle25
Audiosynthesizer, audio_fx, midi, spatial_audio, equalizer15
Data Vizchart, gantt, sankey, voronoi, force_layout, map_engine30
Editorsrich_editor, syntax_editor, diff_editor, spell_check15
Cryptoaes, crypto, jwt_codec, webauthn, oauth2_pkce15
ML/AItensor, embedding, vision_pipeline, pose_detect, nlp_token15
Datacsv, xml, json_patch, pdf, zip, xlsx, markdown20
Typographytext_shaper, bidi, hyphenation, ligature, opentype10
Financialblack_scholes, yield_curve, monte_carlo_var, portfolio_opt25
Bioinformaticsneedleman_wunsch, blast, phylogenetic, allele_freq20
GIScoordinate_transform, great_circle, geohash, wkt_parse20
Post-Quantumkyber_kem, dilithium_sign, sphincs_plus, lattice15
CAD/CAMnurbs_surface, boolean_csg, stl_io, mesh_repair20
Healthcarehl7_fhir, icd_code, drug_interaction, clinical_decision15
Game Engineecs, physics2d, behavior_tree, pathfinding, sprite50+
Testingdom_test, visual_regression, property_test, mock_http20
Build Toolsbundler, minifier, transpiler, source_map, hot_reload15
DevOpsprofiler, debugger, flame_graph, memory_profiler15
…(1,200+ more modules)…

No package manager is needed for these β€” they ship with the Lux runtime.

11.4 External Packages

# lux.toml
[project]
name = "myapp"
version = "0.1.0"

[dependencies]
my-charts = { git = "https://github.com/user/my-charts", tag = "v1.0" }

External packages are resolved by content hash (like Nix), not semver ranges.


12. Energy System

12.1 Ambient Energy Metering

Every operation in Lux is automatically measured in joules. No opt-in required.

energy.total()              # total joules since program start
energy.last()               # joules for the last operation
energy.receipt()            # detailed breakdown by category

12.2 Energy Receipts

An energy receipt is a record with the following structure:

{
  total_joules: float,
  compute_joules: float,
  render_joules: float,
  io_joules: float,
  network_joules: float,
  operations: int,
  duration_ms: float,
  avg_watts: float,
}

12.3 Energy Budgets

Programs can declare energy budgets:

app Main:
  energy.budget(max_joules: 1.0)    # 1 joule per user interaction

  view:
    # if a render cycle exceeds 1J, a warning is emitted

12.4 Energy in HTTP Responses

Server routes automatically include an X-Energy-Joules header:

HTTP/1.1 200 OK
X-Energy-Joules: 0.000342
Content-Type: application/json

{"users": [...]}

12.5 Energy Display Widget

A built-in widget shows live energy consumption:

view:
  energy_meter position=bottom_right

13. Concurrency

13.1 Async / Await

fn fetch_users() -> [User]:
  response = await fetch("https://api.example.com/users")
  await response.json()

13.2 Spawn

spawn:
  process_data(large_dataset)
  print("done processing")

13.3 Channels

ch = channel()

spawn:
  for item in items:
    ch.send(item)
  ch.close()

for item in ch:
  process(item)

14. Error Handling

14.1 Try / Catch

try:
  data = await fetch_data()
  process(data)
catch err:
  print("Error: {err}")

14.2 Result Type

Functions that can fail return Result[T, E]:

fn parse_int(s: str) -> Result[int, str]:
  # ...

match parse_int("42"):
  Ok(n) -> print("got {n}")
  Err(e) -> print("error: {e}")

14.3 The ? Operator

Propagates errors automatically:

fn load_config() -> Result[Config, str]:
  text = read_file("config.toml")?
  parse_toml(text)?

15. CLI Interface

The Lux CLI provides the complete development toolchain:

lux new myapp                # create new project
lux dev                      # development server with hot reload
lux build                    # compile to native binary
lux build --wasm             # compile to WebAssembly
lux run app.lux              # run a Lux program
lux repl                     # interactive REPL
lux test                     # run tests
lux fmt                      # format source files
lux check                    # type-check without compiling
lux energy report.lux        # energy profile of a program
lux add lux:chart            # add standard library module to project

16. Grammar (EBNF)

module        = { item } ;
item          = app_def | server_def | func_def | style_def
              | use_def | type_def | let_stmt ;

app_def       = "app" IDENT ":" INDENT { stmt } [ view_block ] DEDENT ;
server_def    = "server" IDENT { prop_assign } ":" INDENT { route | stmt } DEDENT ;
func_def      = "fn" IDENT "(" [ params ] ")" [ "->" type ] ":" INDENT { stmt } DEDENT
              | "fn" IDENT "(" [ params ] ")" "=" expr ;
style_def     = "style" IDENT ":" INDENT { style_prop | responsive } DEDENT ;
use_def       = "use" path [ "{" ident_list "}" ] [ "as" IDENT ] ;
type_def      = "type" IDENT [ "[" type_params "]" ] ":" INDENT { variant | field } DEDENT ;

view_block    = "view" ":" INDENT { view_node } DEDENT ;
view_node     = IDENT { expr } { prop_assign } { event_bind } [ ":" INDENT { view_node } DEDENT ] ;

route         = method STRING ":" INDENT { stmt } DEDENT ;
method        = "get" | "post" | "put" | "delete" | "patch" ;

stmt          = let_stmt | assign_stmt | if_stmt | for_stmt | while_stmt
              | match_stmt | return_stmt | break_stmt | continue_stmt
              | effect_stmt | expr_stmt ;

let_stmt      = [ "let" ] [ "mut" ] IDENT [ ":" type ] "=" expr ;
assign_stmt   = expr "=" expr ;
if_stmt       = "if" expr ":" INDENT { stmt } DEDENT [ "else" ":" INDENT { stmt } DEDENT ] ;
for_stmt      = "for" IDENT [ "," IDENT ] "in" expr ":" INDENT { stmt } DEDENT ;
while_stmt    = "while" expr ":" INDENT { stmt } DEDENT ;
match_stmt    = "match" expr ":" INDENT { match_arm } DEDENT ;
match_arm     = pattern "->" expr ;
return_stmt   = "return" [ expr ] ;
effect_stmt   = "effect" ":" INDENT { stmt } DEDENT ;

expr          = or_expr ;
or_expr       = and_expr { "or" and_expr } ;
and_expr      = not_expr { "and" not_expr } ;
not_expr      = "not" not_expr | comparison ;
comparison    = additive { ( "==" | "!=" | "<" | "<=" | ">" | ">=" | "is" ) additive } ;
additive      = multiplicative { ( "+" | "-" | "++" ) multiplicative } ;
multiplicative = unary { ( "*" | "/" | "%" ) unary } ;
unary         = ( "-" | "not" ) unary | postfix ;
postfix       = primary { "." IDENT | "[" expr "]" | "(" [ args ] ")" | "?" } ;
primary       = INT | FLOAT | STRING | "true" | "false" | "none"
              | IDENT | "(" expr ")" | list_lit | record_lit
              | "signal" "(" expr ")" | "memo" "->" expr
              | lambda | if_expr | match_expr | "do" ":" block ;

lambda        = IDENT "->" expr | "(" params ")" "->" expr ;
if_expr       = "if" expr ":" expr "else" ":" expr ;
match_expr    = "match" expr ":" INDENT { match_arm } DEDENT ;
list_lit      = "[" [ expr { "," expr } ] "]" ;
record_lit    = "{" [ field_init { "," field_init } ] "}" ;
field_init    = IDENT ":" expr | IDENT ;

type          = simple_type [ "?" ] ;
simple_type   = IDENT | "[" type "]" | "(" type { "," type } ")"
              | "{" field_type { "," field_type } "}" | "fn" "(" [ type_list ] ")" "->" type ;
field_type    = IDENT ":" type ;

prop_assign   = IDENT "=" expr ;
event_bind    = "on" ":" IDENT "->" expr ;

params        = param { "," param } ;
param         = IDENT [ ":" type ] [ "=" expr ] ;
args          = expr { "," expr } ;
path          = ( "./" | IDENT ":" ) IDENT { "/" IDENT } ;

17. Compilation Model

17.1 Pipeline

.lux source
  β†’ Lexer (indentation-aware tokenizer)
  β†’ Parser (recursive descent + precedence climbing)
  β†’ AST
  β†’ Type checker (Hindley-Milner inference + gradual typing)
  β†’ IR (register-based SSA, ViewNode β†’ VDOM ops, styles β†’ CSS)
  β†’ Optimizer (constant folding, dead code, signal dependency analysis)
  β†’ Codegen (Cranelift for native, WASM for browser)

17.2 View Compilation

View blocks compile to VDOM builder calls:

# Lux source
view:
  column gap=16:
    text "Hello" size=24

# Compiles to (conceptual)
VNode::element("column")
  .attr("gap", "16")
  .child(
    VNode::element("text")
      .attr("size", "24")
      .child(VNode::text("Hello"))
  )

17.3 Reactive Compilation

Signals, memos, and effects compile to the reactive runtime:

# Lux source
count = signal(0)
doubled = memo -> count * 2

# Compiles to (conceptual)
let (count_r, count_w) = create_signal(0);
let doubled = create_memo(move || count_r.get() * 2);

17.4 Style Compilation

Style properties compile to optimized CSS:

# Lux source
text "Hello" size=24 color=blue weight=bold

# Emits CSS class
.lx_a { font-size: 24px; color: blue; font-weight: bold; }

CSS is deduplicated at compile time β€” identical style combinations share a single class.


18. Standard Library API Conventions

All standard library modules follow these conventions:

  1. Constructor: Module.new(config) or direct function call
  2. Fluent API: Methods return self for chaining
  3. Energy tracking: Every public method is metered
  4. JSON interop: All types serialize to/from JSON via to_json() and from_json()
  5. Error handling: Fallible operations return Result[T, str]

Example:

use lux:black_scholes

price = black_scholes.price(
  spot: 100.0,
  strike: 105.0,
  rate: 0.05,
  vol: 0.2,
  expiry: 1.0,
  kind: "call"
)

greeks = black_scholes.greeks(spot: 100.0, strike: 105.0, rate: 0.05, vol: 0.2, expiry: 1.0)
print("Delta: {greeks.delta}, Gamma: {greeks.gamma}")

19. Interoperability

19.1 JavaScript Interop (Browser)

# Call JavaScript from Lux
extern fn alert(msg: str)
extern fn console_log(msg: str)

# Access DOM (escape hatch β€” prefer Lux view system)
extern fn document_query(selector: str) -> Element?

19.2 Rust Interop (Native)

Lux modules compile to Rust-compatible types. Rust crates can be used via:

use rust:serde_json
use rust:tokio

19.3 HTTP / REST

response = await fetch("https://api.example.com/data",
  method: "POST",
  headers: {"Content-Type": "application/json"},
  body: {name: "David"}
)
data = await response.json()

20. Testing

20.1 Test Syntax

test "addition works":
  assert 2 + 2 == 4

test "greeting includes name":
  result = greet("David")
  assert result == "Hello, David!"

test "signal updates":
  count = signal(0)
  count.set(5)
  assert count == 5

20.2 Running Tests

lux test                     # run all tests
lux test utils.lux           # run tests in a specific file
lux test --filter "addition" # filter by test name

Appendix A: Complete Example β€” Todo Application

# todo.lux β€” Complete todo application in Lux

app TodoApp:
  todos = signal([])
  input_text = signal("")
  filter_mode = signal("all")

  fn add_todo():
    if input_text != "":
      todos.update(list -> list ++ [{
        text: input_text,
        done: false,
        id: list.len()
      }])
      input_text.set("")

  fn toggle(id: int):
    todos.update(list ->
      list.map(t -> if t.id == id: {..t, done: not t.done} else: t)
    )

  fn remove(id: int):
    todos.update(list -> list.filter(t -> t.id != id))

  visible = memo ->
    match filter_mode:
      "all" -> todos
      "active" -> todos.filter(t -> not t.done)
      "done" -> todos.filter(t -> t.done)

  remaining = memo -> todos.filter(t -> not t.done).len()

  view:
    column padding=24 max_width=600 margin="0 auto":
      text "Todos" size=32 weight=bold color="#333"

      row gap=8:
        input bind=input_text
          placeholder="What needs to be done?"
          on:enter -> add_todo()
        button "Add" on:click -> add_todo()

      for todo in visible:
        row gap=8 key=todo.id padding=8 border_bottom="1px solid #eee":
          checkbox bind=todo.done on:change -> toggle(todo.id)
          text todo.text
            strike=todo.done
            color=if todo.done: "#999" else: "#333"
          button "x" on:click -> remove(todo.id)
            color=red
            opacity=0.5

      row gap=16 padding=8:
        text "{remaining} remaining" color="#666"
        row gap=8:
          button "All" on:click -> filter_mode.set("all")
            weight=if filter_mode == "all": bold else: normal
          button "Active" on:click -> filter_mode.set("active")
            weight=if filter_mode == "active": bold else: normal
          button "Done" on:click -> filter_mode.set("done")
            weight=if filter_mode == "done": bold else: normal

      energy_meter position=bottom_right

Appendix B: Comparison β€” Lux vs React+TypeScript

Lines of Code for a Todo App

React + TypeScript + CSSLux
Markup/View45 lines (JSX)β€”
Logic35 lines (TypeScript)β€”
Styles60 lines (CSS)β€”
Imports/Config15 linesβ€”
Total155 lines across 3 files55 lines in 1 file
Dependenciesreact, react-dom, typescript, webpack, babel, css-loader0
node_modules~180 MB0 bytes
Bundle size~45 KB (minified + gzipped)~12 KB (WASM)
Energy per renderunmeasured0.000004 J

Concept Mapping

Web StackLux
HTML elementsView elements (text, column, row, box)
CSS classesStyle properties on elements / named style blocks
CSS media queries@width < 768: in style blocks
JavaScriptLux expressions and statements
TypeScript typesLux type annotations (optional)
React useStatesignal()
React useMemomemo ->
React useEffecteffect:
React componentsfn Component(props):
JSXView blocks
classNamestyle=name
onClickon:click ->
package.jsonlux.toml
npm installNot needed β€” 1,723 modules built in
webpack/vitelux build
node_modulesDoes not exist