Variables, templates, file loading, executables, and built-in methods form mlld's core syntax. exe defines reusable functions (js, node, python, shell, prose). run invokes them. => returns values from scripts and blocks.
Variables
var vs exe: var creates values (no parameters). exe creates functions (takes parameters).
var @name = "Alice" >> value - no params
exe @greet(who) = `Hi @who!` >> function - takes params
>> Use var for computed values, exe for reusable functions
var @result = @greet(@name) >> "Hi Alice!"
Primitives, arrays, objects:
var @n = 42
var @price = 19.99
var @ok = true
var @arr = [1, 2, 3]
var @obj = {"key": "value"}
var @merged = { ...@obj, "extra": 1 } >> object spread
exe @add(a, b) = js { return a + b }
var @sum = @add(@n, 8) >> 50 (number preserved)
var @date = cmd {date} >> command result
var @readme = <README.md> >> file contents
See Also
- Conditional Inclusion -
@var?omission and nullish fallback patterns. - Templates Basics - Variable interpolation in template strings.
- Exe Simple Forms - When to switch from
varvalues to reusableexefunctions.
Conditional Inclusion
Conditional inclusion (@var?): omit content when a variable is falsy.
Nullish fallback in templates (@var??"default"): use tight ?? binding in template interpolation.
Nullish fallback in expressions (@a ?? @b): use spaced ?? in var/let expressions and chains.
var @tools = "json"
var @empty = ""
>> In commands: @var?`...`
run cmd { echo @tools?`--tools "@tools"` @empty?`--empty` }
>> Output: --tools json (--empty omitted)
>> In templates: omit the variable itself
var @title = "MyTitle"
var @msg = `DEBUG:@title?`
>> In templates: tight null coalescing
var @missing = null
var @hello = `Hello,@missing??"friend"`
>> In expressions: spaced null coalescing (chaining works)
var @primary = null
var @secondary = "backup"
var @chosen = @primary ?? @secondary ?? "fallback"
>> In arrays
var @list = [@a, @b?, @c] >> @b omitted if falsy
>> In objects
var @obj = {"name": @n, "title"?: @t} >> title omitted if @t falsy
Truthiness Rules
Truthiness: falsy = null, undefined, "", "false", "0", 0, NaN, [], {}
Templates
var @message = `Hello @name, welcome!`
var @doc = ::Use `mlld` to orchestrate::
>> Multi-line
var @report = `
Status: @status
Config: <@root/config.json>
Data: @data|@parse
`
External Template Files (.att)
For templates longer than a simple paragraph, use .att files with the exe ... = template pattern:
exe @welcome(name, role) = template "./prompts/welcome.att"
exe @systemPrompt(context) = template "./prompts/system.att"
>> Call like any other function
show @welcome("Alice", "admin")
The .att file uses @var for interpolation and supports full mlld syntax inside:
>> prompts/welcome.att
Hello @name!
Your role: @role
/for @perm in @role.permissions
- @perm
/end
Key points:
- Parameters from
exe @func(params)are automatically available as@paramin the template .attsupports<file.md>references and/for ... /endtemplate loops- In template content, use condensed pipe syntax (
@value|@pipe) to avoid ambiguity - Relative
<file>paths inside a template file resolve from that template file's directory - Use
@@or\@to output a literal@symbol (e.g.,user@@example.com) - Never load
.attfiles with angle brackets - useexe ... = templateinstead
When to use external templates:
- Prompts longer than 2-3 lines
- Templates with complex structure or loops
- Reusable prompt components
- Agent system prompts
Alternative syntax - .mtt files:
If .att syntax conflicts with your content, use .mtt files instead. Common case: prompts that include @path/to/file references for LLMs to interpret:
exe @codeReview(files, instructions) = template "./prompts/review.mtt"
>> prompts/review.mtt - uses {{var}} syntax
Review these files: {{files}}
{{instructions}}
Reference @src/utils.ts and @tests/utils.test.ts for context.
The @src/utils.ts stays literal - mlld won't try to interpolate it.
| Extension | Variable syntax | File refs | Use when |
|---|---|---|---|
.att |
@var |
<file.md> |
Default, mlld-style |
.mtt |
{{var}} |
None | Content has @path or <tag> meant for LLMs |
| Feature | .att |
.mtt |
|---|---|---|
| Pipes in template body | Condensed only (`@var | @pipe`) |
| Loops | /for ... /end |
Not supported |
Loops in Templates
Template-embedded vs top-level: Control flow inside templates uses /for and /end with slash prefix. Top-level directives are separate statements.
>> TOP-LEVEL: for as a directive (produces output directly)
for @item in @items => show `- @item.name`
>> TEMPLATE-EMBEDDED: /for inside template body
var @items = ["alpha", "beta", "gamma"]
var @toc = ::
/for @item in @items
- @item
/end
::
show @toc
Key/value form in templates:
/for @k, @v in @items
- @k: @v
/end
Key differences:
| Context | Syntax | Notes |
|---|---|---|
| Top-level | for @x in @y => action |
Directive on own line |
| In template | /for @x in @y ... /end |
Slash prefix required at line start |
Template-embedded rules:
/forand/endmust start at column 1 of their line inside the template- No
=>arrow - the lines between/forand/endare the body - Only
/forand/endare valid inside templates - no other directives
File Loading
>> Basic loading
var @content = <README.md>
var @config = <config.json> >> auto-parsed as object
var @events = <events.jsonl> >> auto-parsed as array of JSON objects
var @author = <package.json>.author >> field access
show @events[0].event >> first JSONL record
>> Globs (returns array)
var @docs = <docs/**/*.md>
show @docs.length
for @doc in @docs => show @doc.mx.filename
>> JSON globs - each item is auto-parsed
var @configs = <configs/*.json>
var @first = @configs[0]
show @first.name >> access parsed JSON
show @first.mx.data.name >> explicit parsed-data view
show @first.mx.text >> raw file text
show @first.mx.filename >> file metadata still available
>> With "as" template
var @toc = <docs/*.md> as "- [<>.mx.fm.title](<>.mx.relative)"
Structured values loaded from files are data-first:
@config.apiUrland@config.mx.data.apiUrlare equivalent.@config.mx.textis the raw text representation..mxstays reserved for wrapper metadata; if payload data has anmxkey, use@config.mx.data.mx.
Section selection - extract markdown heading blocks:
>> Single section (includes child headings)
var @overview = <guide.md # Overview>
>> Multi-section include list (comma-separated)
var @selected = <guide.md # "Quick Start", Other>
>> Include/exclude sets
var @filtered = <guide.md # "TL;DR", "Titled section", Other; !# tldr, "Another section title">
>> Optional include (no error if missing)
var @maybe = <guide.md # "Migration Notes"?, "Quick Start">
>> Fuzzy heading matching (case/punctuation-insensitive, prefix-based)
var @tldr = <guide.md # tldr> >> matches heading "TL;DR"
Rules:
- Include items are comma-separated after
#. - Exclude items are comma-separated after
; !#(or; #). - Use quotes for multi-word titles and punctuation-heavy titles.
- Add trailing
?to an include selector to avoid errors when missing. - Missing non-optional include selectors throw an error.
- Heading matching is fuzzy prefix-based and picks the first heading match.
- Renaming with
asis only supported when one section is selected. - Use
; !#to start excludes.<file.md # tl;dr !# later>is invalid.
Optional load - returns null for missing files, empty array for empty globs:
>> Missing file returns null
var @optional = <config.json>?
when @optional => show "Config loaded"
>> Empty glob returns []
var @matches = <*.nonexistent>?
show @matches.length >> 0
>> Existing file works normally
var @readme = <README.md>?
show @readme >> file content
See Also
- File Loading Metadata - Access
.mx.filename,.mx.relative, and related metadata. - File Loading AST - Parse source into AST for structural analysis.
- Variables Basics - Combine file-loaded values with standard variable patterns.
AST Selection
AST Selection (extract code from files):
>> Exact names
var @handler = <src/api.ts { createUser }>
>> Wildcards
var @handlers = <api.ts { handle* }> >> prefix match
var @validators = <api.ts { *Validator }> >> suffix match
>> Type filters
var @funcs = <service.ts { *fn }> >> all functions
var @classes = <service.ts { *class }> >> all classes
>> Name listing (returns string arrays)
var @names = <api.ts { ?? }> >> all definition names
var @funcNames = <api.ts { fn?? }> >> function names only
Supported: .js, .ts, .jsx, .tsx, .py, .go, .rs, .java, .rb
Type keywords: fn, var, class, interface, type, enum, struct
File Metadata
Metadata fields (via .mx):
var @file = <README.md>
show @file.mx.filename >> "README.md"
show @file.mx.relative >> relative path from cwd
show @file.mx.absolute >> absolute path
show @file.mx.path >> alias for absolute path
show @file.mx.dirname >> parent directory name
show @file.mx.relativeDir >> relative path to directory
show @file.mx.absoluteDir >> absolute path to directory
show @file.mx.tokens >> token count estimate
show @file.mx.fm.title >> frontmatter field
show @file.mx.text >> raw text representation
show @file.mx.fm >> frontmatter object
For frontmatter metadata, use .mx.fm:
@file.mx.fm.titleaccesses thetitlefrontmatter field.- Use
.mx.textand.mx.fmwhen you need explicit control. - If frontmatter has an
mxkey, access it with@file.mx.fm.mx.
In loops - metadata works directly:
var @files = <docs/*.md>
for @file in @files => show @file.mx.filename
for @file in @files => show @file.mx.fm.status
JSON Parsing
Parse JSON explicitly with transforms:
>> When you have a JSON string and need to parse it
var @jsonStr = '[{"name":"Alice"},{"name":"Bob"}]'
var @parsed = @jsonStr | @parse
show @parsed.0.name >> "Alice"
>> When you have an object and need the JSON string
var @obj = {"name": "Alice"}
var @str = @obj | @parse
show @str >> '{"name":"Alice"}'
>> Common in pipelines with LLM responses
var @response = @llm("return JSON") | @parse.llm
var @items = @response
Frontmatter
YAML metadata block at the top of any mlld file:
---
name: my-module
author: alice
version: 1.0.0
about: Brief description
license: CC0
---
Access via @fm:
var @id = @fm.id
var @version = @fm.version
>> Build a metadata object
var @meta = {
id: @fm.id,
name: @fm.name,
version: @fm.version
}
exe
exe vs var: exe defines functions (takes parameters). var creates values (no parameters). Use exe when you need to pass arguments; use var for computed values.
mlld validate warns when exe parameters use generic names such as result, output, or data because they can shadow caller variables. Prefer specific names such as status, finalOutput, or inputData.
Simple forms:
>> Command
exe @list(dir) = cmd {ls -la @dir | head -5}
>> JavaScript (in-process)
exe @add(a, b) = js { return a + b }
>> Node.js (VM-isolated)
exe @hash(text) = node {
const crypto = require('crypto');
return crypto.createHash('sha256').update(text).digest('hex');
}
>> Python
exe @add(a, b) = py { print(a + b) }
exe @calculate(x, y) = py {
result = x ** 2 + y ** 2
print(result)
}
>> Template
exe @greet(name) = `Hello @name!`
>> External template file
exe @welcome(name, role) = template "./prompts/welcome.att"
>> Prose (requires config)
exe @analyze(data) = prose:@config { session "Analyze @data" }
Python notes:
- Use
print()to return values (captured as string output) - Parameters preserve their original mlld types (
int,float,bool,str,list,dict,None) - Standard library available:
import json,import math, etc. - Cast only when you intentionally need conversion
Labels: Add labels before the name for runtime behavior. The llm label enables automatic checkpointing:
exe llm @review(file) = template "review.att"
Cached results persist across runs. Use --resume @review to selectively re-call. See mlld howto checkpoint.
See Also
- Exe Blocks - More
exebody styles including multi-step blocks. - Run Basics -
rundirective patterns and when to preferrunoverexe. - Run Params - Parameter passing semantics for command and shell execution.
Script Return
=> @value terminates script execution immediately. When => appears inside a top-level if or when block, it terminates the script, not just the block.
Basic script return:
>> Strict mode
=> "final result"
show "unreachable"
Script return from conditionals:
if @condition [
=> "early exit"
]
=> "fallback"
var @mode = "fast"
when @mode == "fast" => [
if @ready [
=> "immediate"
]
]
=> "deferred"
Module default export:
Imported .mld modules expose script return values through default:
>> module.mld
var @status = "active"
=> { code: 200, status: @status }
var @unreachable = "never runs"
>> main.mld
import { default as @result, status as @s } from "./module.mld"
show @result.code >> 200
show @s >> "active"
Scripts without => do not emit implicit final return output. Use => explicitly when module consumers need a default export value.
In exe blocks:
=> @value returns from the function body. See exe-blocks for function return syntax.
exe @validate(input) = [
if !@input [
=> { error: "missing" }
]
=> { ok: @input }
]
Exe Metadata
Executables can include metadata for tooling and type safety.
Typed parameters:
exe @greet(name: string, times: number) = js { return "Hello " + name; }
exe @process(data: object, format: string) = js { return data; }
exe @count(items: array) = js { return items.length; }
Type annotations use : type syntax after the parameter name. Supported types: string, number, boolean, object, array.
Description metadata:
exe @greet(name: string) = js { return "Hello " + name; } with { description: "Greet a user by name" }
The with { description: "..." } clause adds a description that appears in MCP tool listings.
Combined example:
exe @searchIssues(repo: string, query: string, limit: number) = cmd {
gh issue list -R @repo --search "@query" -L @limit --json number,title
} with { description: "Search GitHub issues by query" }
MCP integration:
When exported as tools, type annotations and descriptions generate JSON Schema for the MCP tool listing. See mcp-export for serving and mcp-tool-gateway for tool collections.
Parameter types vs runtime:
Type annotations are metadata for tooling. At runtime, parameters arrive as their actual types from the caller.
Exe Block Syntax
Block syntax (multi-statement bodies):
exe @process(data) = [
let @validated = @validate(@data)
let @transformed = @transform(@validated)
=> @transformed
]
>> With accumulation
exe @countItems(items) = [
let @count = 0
for @item in @items [
let @count += 1
]
=> @count
]
Block rules:
- Use
[...]for multi-statement bodies let @var = valuefor block-scoped variableslet @var += valuefor accumulation (arrays/strings/objects)=> valuerequired as last statement for return
Exe When
When in exe (first match wins):
exe @classify(score) = when [
@score >= 90 => "A"
@score >= 80 => "B"
* => "F"
]
Use if for imperative flow where multiple branches can run.
When when appears as a statement inside an exe block, matched value actions return from the enclosing exe.
Use block-form return to make that intent explicit:
exe @guard(x) = [
when !@x => [
=> "missing"
]
=> "ok"
]
Exe Shadow Environments
Shadow environments expose helpers to all code blocks of that language:
>> JavaScript shadow environment
exe @double(n) = js { return n * 2 }
exe @cap(s) = js { return s[0].toUpperCase() + s.slice(1) }
exe js = { double, cap } >> expose to all js blocks
var @out = js { cap("hello") + ": " + double(5) } >> "Hello: 10"
>> Python shadow environment
exe @square(x) = py {
result = int(x) ** 2
print(result)
}
exe @greet(name) = py {
print(f"Hello, {name}!")
}
exe py = { square, greet } >> expose to all py blocks
run py {
square(4) >> prints 16
greet("World") >> prints Hello, World!
}
Cross-function calls: Shadow functions can call each other:
exe @add(a, b) = py { print(int(a) + int(b)) }
exe @multiply(x, y) = py { print(int(x) * int(y)) }
exe py = { add, multiply }
exe @calculate(n) = py {
sum_result = add(n, 10) >> calls shadow function
product = multiply(n, 2) >> calls shadow function
print(f"Sum: {sum_result}, Product: {product}")
}
exe py = { add, multiply, calculate } >> update to include calculate
Supported languages: js, node, py/python
Exe Prose Syntax
Prose execution invokes LLM-interpreted DSL skills (OpenProse or custom):
import { @opus } from @mlld/prose
>> Inline (interpolates like templates)
exe @summarize(text) = prose:@opus { summarize @text }
>> File reference (.prose files do NOT interpolate)
exe @review(code) = prose:@opus "./review.prose"
>> Template files (.prose.att or .prose.mtt interpolate)
exe @greet(name) = prose:@opus "./greet.prose.att"
Interpolation rules:
prose:@config { inline }- interpolates@varlike templates"file.prose"- no interpolation, raw content"file.prose.att"- ATT interpolation (@var)"file.prose.mtt"- MTT interpolation ({{var}})
See mlld howto prose for setup, OpenProse syntax, and custom interpreters.
run
Decision tree:
- Single line + pipes only →
cmd { ... }(safe, recommended) - Needs
&&,||, control flow →sh { ... }(full shell) - JavaScript code →
js { ... }(in-process) ornode { ... }(VM-isolated) - Python code →
py { ... }orpython { ... }(subprocess)
Executable call statement sugar:
- Strict mode (
.mld):@task()executes with the same behavior asrun @task() - Markdown mode (
.md):/@task()executes with the same behavior as/run @task() show @task()still displays output explicitly
>> cmd (pipes only, safe)
run cmd {echo Hello | tr '[:lower:]' '[:upper:]'}
var @date = cmd {date}
>> sh (full shell scripts)
run sh {
npm test && npm run build || echo "Build failed"
}
>> JavaScript (in-process, fast)
run js {console.log("hello")}
var @result = js {return 42}
>> Node.js (VM-isolated, full API)
var @hash = node {
const crypto = require('crypto');
return crypto.createHash('md5').update('hello').digest('hex');
}
>> Python (subprocess)
run py {print("hello from python")}
var @sum = py {
result = 2 + 2
print(result)
}
Language comparison:
| Language | Isolation | Use case |
|---|---|---|
js |
None (in-process) | Fast calculations, simple transforms |
node |
VM context | Full Node.js API, require() |
py/python |
Subprocess | Python libraries, data science |
Run Command Working Directory
Working directory override:
run cmd:/ {pwd} >> runs in /
var @myPath = "/tmp"
run cmd:@myPath {pwd} >> runs in /tmp
run cmd:~ {pwd} >> runs in home directory
run sh:/tmp {pwd} >> runs in /tmp
run js:/tmp {console.log(process.cwd())}
run cwd supports absolute paths, @var references, and ~ home expansion.
Run Command Stdin
Stdin support:
var @data = '[{"name":"Alice"}]'
run cmd { cat | jq '.[]' } with { stdin: @data }
>> Pipe sugar (equivalent)
run @data | { cat | jq '.[]' }
Run Parameter Syntax
Parameter syntax by language:
cmd: interpolate with@paramsh: use shell variables as$paramjs/node: parameters as JavaScript variablespy/python: parameters as Python variables (typed values)
>> JavaScript - parameters are typed values
exe @process(items) = js { return items.map(x => x * 2) }
var @result = @process([1, 2, 3]) >> [2, 4, 6]
>> Python - parameters are typed values
exe @add(a, b) = py { print(a + b) }
var @sum = @add(5, 3) >> 8
>> Python with complex types (metadata preserved)
exe @analyze(data) = py {
import json
print(json.dumps(data))
}
Python variable helpers (available as mlld global, no import needed):
mlld.is_variable(x)- check if value is a wrapped mlld Variable- Variables have
__mlld_type__and__mlld_metadata__attributes - Arrays/lists and dicts pass through with metadata preserved
Builtins
Reserved Variables
@root/@base- project root path@now- current ISO timestamp@input- stdin/env (must be allowed in config)@state- mutable state for SDK integrations@debug- environment info@fm- current file's frontmatter (in modules)@mx- metadata accessor (labels, taint, guard context)
@root/@base resolution algorithm:
- Start at the current file directory and walk upward.
- Check mlld markers first (in order):
mlld-config.jsonmlld-lock.jsonmlld.lock.json
- If none are found, check fallback project markers:
package.json.gitpyproject.tomlCargo.toml
- The first directory containing any marker becomes
@root(@baseis an alias).
Built-in transformer/helper names are available for var/let declarations and are shadowable per scope:
@exists,@fileExists,@typeof@parse,@json(deprecated alias),@xml,@csv,@md@upper,@lower,@trim,@pretty,@sort@keep,@keepStructured
mlld validate reports builtin shadowing as informational output.
Transformers
Used with | in pipelines. Each receives the previous value.
@parse- parse JSON (default mode)@parse.strict- strict JSON only@parse.loose- single quotes, trailing commas@parse.llm- extract JSON from LLM response text@xml- parse XML@csv- parse CSV@md- parse markdown@upper- uppercase string@lower- lowercase string@trim- strip whitespace@pretty- pretty-print JSON@sort- sort array
var @users = cmd { cat users.json } | @parse
var @extracted = @llmResponse | @parse.llm
var @clean = @raw | @trim | @lower
Checks
@exists(target)- returnstrue/false. Works with file paths, variables, object fields, array indices, and globs. Note:@exists(@var)checks if the variable is defined, not if a file at that path exists.@fileExists(path)- returnstrue/false. Always resolves argument to a path string, then checks filesystem. Unlike@exists(@var),@fileExists(@var)resolves the variable and checks the file.@typeof(value)- returns type as string@typeInfo(value)- returns rich type/provenance details for debugging
>> File existence (literal paths)
if @exists("config.json") [ show "config found" ]
if @exists(<src/**/*.ts>) [ show "has ts files" ]
>> Variable/field existence
if @exists(@obj.field) [ show "field defined" ]
if @exists(@arr[5]) [ show "index 5 exists" ]
>> File existence with dynamic paths
var @configPath = "config.json"
if @fileExists(@configPath) [ show "config found" ]
if @fileExists(@settings.configPath) [ show "found" ]
Type Checks
.isArray()/.isObject()/.isString().isNumber()/.isBoolean()/.isNull()/.isDefined()
Methods
String methods:
@str.length- string length@str.includes(sub)- true if contains substring@str.indexOf(sub)- index or -1@str.startsWith(prefix)/endsWith(suffix)@str.toLowerCase()/toUpperCase()@str.trim()- remove whitespace@str.split(separator)- split to array@str.slice(start, end?)- extract substring by position@str.substring(start, end?)- extract substring (no negative indices)@str.replace(search, replacement)- replace first match (accepts string or regex)@str.replaceAll(search, replacement)- replace all matches (accepts string or regex)@str.replaceAll({"old": "new", ...})- bulk replacement via object map (applied sequentially)@str.match(pattern)- match against string or regex, returns match array or null@str.padStart(length, char?)/padEnd(length, char?)- pad to target length@str.repeat(count)- repeat string N times
Array methods:
@arr.length- array length@arr.includes(value)- true if contains value@arr.indexOf(value)- index or -1@arr.join(separator)- join to string@arr.slice(start, end?)- extract sub-array by position@arr.concat(other)- combine arrays@arr.reverse()- reverse order (returns new array)@arr.sort()- sort alphabetically (returns new array)@arr.flat(depth?)- flatten nested arrays (default depth 1)@arr.at(index)- element at index (supports negative indices)
Helpers:
@keep(obj, [...keys])- keep only specified keys from object@keepStructured(obj, schema)- keep keys matching a schema structure
Method chains can continue across lines when continuation lines start with .
var @fruits = ["apple", "banana", "cherry"]
var @message = "Hello World"
show @fruits.includes("banana") >> true
show @fruits.join(" and ") >> "apple and banana and cherry"
show @message.toLowerCase() >> "hello world"
show @message.split(" ") >> ["Hello", "World"]
>> Method chaining
show @message.trim().toLowerCase().startsWith("hello") >> true
>> Multiline method chaining
exe @normalize(text) = @text
.trim()
.toLowerCase()
.replace("hello", "hi")
Comments
Use >> at start of line or << at end.
>> This is a comment
var @x = 5 << end-of-line comment
show @x >> also works here
Escaping
Special characters:
| Character | In templates | Solution |
|---|---|---|
@ |
Variable interpolation | \@ or helper function |
` |
Template delimiter | Use ::...:: syntax |
:: |
Alternate delimiter | Use triple-colon :::...::: |
{{}} |
Object expression | Use triple-colon escape |
Escape hatch templates:
>> Triple-colon disables all @ interpolation — treats @ as literal text
var @xml = :::<USER id="@id">@name</USER>:::
>> Useful for XML/HTML with @ attributes
var @elem = :::<input type="email" placeholder="user@example.com">:::
When to use each:
- Backticks
`...`- Default template, simple interpolation - Double-colon
::...::- When content has backticks - Triple-colon
:::...:::- When content triggers file detection (has @ with dots/slashes)
See Also
- Escaping @ Symbols -
\@and@@patterns for literal at-sign output. - Escaping Defaults - Default interpolation/escape behavior.
- Templates Basics - Template forms and interpolation rules.
Escaping @ Symbol
Use \@ or @@ to output a literal @. Works in all string and template contexts.
var @email = `user\@example.com` >> "user@example.com"
var @email = `user@@example.com` >> "user@example.com"
In .att template files:
Contact: user@@example.com
Follow @@username on Twitter
The exception is sh {} / bash {} blocks — content is passed to the shell verbatim with no @ processing.
Default Values
Problem: Need default values when data might be missing.
Solutions:
>> 1. Ternary expressions
var @name = @user.name ? @user.name : "Anonymous"
>> 2. JavaScript helper for safe access
exe @safe(obj, field, fallback) = js {
return (obj && obj[field] !== undefined) ? obj[field] : (fallback || null)
}
var @title = @safe(@item, "title", "Untitled")
>> 3. Safe array access
exe @safeArr(obj, field) = js {
return (obj && Array.isArray(obj[field])) ? obj[field] : []
}
var @tags = @safeArr(@post, "tags")
>> 4. Conditional inclusion with @var?
var @subtitle? = @item.subtitle >> omit if falsy
show `Title: @item.title @subtitle?` >> subtitle only if present
Common patterns:
>> Optional chaining equivalent
exe @get(obj, path, fallback) = js {
const keys = path.split('.');
let val = obj;
for (const k of keys) {
if (val == null) return fallback;
val = val[k];
}
return val ?? fallback;
}
var @city = @get(@user, "address.city", "Unknown")