Lavash.Rx.Transpiler (Lavash v0.4.0-rc.1)

Copy Markdown View Source

Translates Elixir expressions to JavaScript.

This module handles the core transpilation of Elixir AST to JavaScript code, enabling optimistic client-side evaluation of reactive expressions.

Supported Constructs

Literals

  • Strings, numbers, booleans, nil, atoms
  • Lists and maps

Operators

  • Comparison: ==, !=, >, <, >=, <=
  • Logical: &&, ||, and, or, not, !
  • Arithmetic: +, -, *, /
  • String: <> (concatenation)
  • List: ++ (concatenation), in (membership)
  • Pipe: |>

Control Flow

  • if cond, do: x, else: y → ternary operator

State References

  • @variablestate.variable
  • @nested.fieldstate.nested.field

String Functions

Enum Functions

Map Functions

  • Map.get/2,3

Utility Functions

Usage

iex> Lavash.Transpiler.to_js("@count + 1")
"(state.count + 1)"

iex> Lavash.Transpiler.to_js("if @active, do: \"on\", else: \"off\"")
"(state.active ? \"on\" : \"off\")"

Validation

Use validate/1 to check if an expression can be fully transpiled:

iex> Lavash.Transpiler.validate("@count + 1")
:ok

iex> Lavash.Transpiler.validate("Ash.read!(Product)")
{:error, "Ash.read!"}

Summary

Functions

Translates an Elixir AST to JavaScript.

Translates an Elixir expression string to JavaScript.

Checks if an AST can be transpiled to JavaScript.

Transpiles a run function body to JavaScript statements.

Validates that an Elixir expression can be transpiled to JavaScript.

Validates an AST for transpilability.

Functions

ast_to_js(ast)

@spec ast_to_js(Macro.t()) :: String.t()

Translates an Elixir AST to JavaScript.

This is the lower-level function that works directly with AST. Use to_js/1 for string input.

to_js(code)

@spec to_js(String.t()) :: String.t()

Translates an Elixir expression string to JavaScript.

Returns the JavaScript code as a string. Untranspilable expressions are converted to undefined with a comment indicating the issue.

Examples

iex> Lavash.Transpiler.to_js("@count")
"state.count"

iex> Lavash.Transpiler.to_js("length(@items)")
"(state.items.length)"

iex> Lavash.Transpiler.to_js("if @a, do: 1, else: 2")
"(state.a ? 1 : 2)"

transpilable?(ast)

@spec transpilable?(Macro.t()) :: boolean()

Checks if an AST can be transpiled to JavaScript.

Returns true if transpilable, false otherwise. Use validate/1 or validate_ast/1 to get the specific error.

transpile_run_body(ast)

@spec transpile_run_body(Macro.t()) :: {[String.t()], String.t()}

Transpiles a run function body to JavaScript statements.

This handles the run [:reads], fn assigns -> ... end pattern, where:

  • assigns.field accesses → state.field
  • x = expr bindings → const x = expr;
  • assign(assigns, :field, value) → adds to return object
  • Piped assigns with multiple assign/3 calls → combined return object

Returns a tuple of {statements, return_expr} where statements is a list of JS statements (const declarations) and return_expr is the return object.

Examples

# Simple case
iex> body = quote do
...>   discount = assigns.subtotal * assigns.discount_rate
...>   assigns |> assign(:discount_amount, discount) |> assign(:total, assigns.subtotal - discount)
...> end
iex> Lavash.Rx.Transpiler.transpile_run_body(body)
{["const discount = (state.subtotal * state.discount_rate);"],
 "{discount_amount: discount, total: (state.subtotal - discount)}"}

validate(source)

@spec validate(String.t()) :: :ok | {:error, String.t()}

Validates that an Elixir expression can be transpiled to JavaScript.

Returns :ok if the expression is fully transpilable, or {:error, description} if it contains unsupported constructs.

Examples

iex> Lavash.Rx.Transpiler.validate("length(@tags)")
:ok

iex> Lavash.Rx.Transpiler.validate("Ash.read!(Product)")
{:error, "Ash.read!"}

validate_ast(ast)

@spec validate_ast(Macro.t()) :: :ok | {:error, String.t()}

Validates an AST for transpilability.

Returns :ok if transpilable, {:error, reason} otherwise.