Weblang
Weblang is a programming language built around variables, pipe functions, and pipe chains. Its source syntax is based on YAML. Programs execute assignments in order, passing values through functions provided by the host.
=name: Vidar
=message: Hello $name |> up
With an up function registered by the host, this program returns HELLO VIDAR. Conditions, loops, arithmetic, I/O, and error recovery are implemented in host functions. Weblang has no separate call, return, or control-flow syntax.
Run
Use the .w extension for source files, such as app.w.
npm i -g weblang
weblang app.w
The CLI prints the program's return value: the last assigned result by default. Errors print to stderr and exit with status 1. The CLI does not register pipe functions. Programs using pipes run through the host API.
For example, this program runs directly from the command line:
=name: Vidar
=message: Hello $name
It prints Hello Vidar.
Assignments
=name: value evaluates a value and assigns it to a variable. Assignments run in source order and may replace earlier values.
=name: Vidar
=message: Hello $name
=name: Storm
Here, message remains Hello Vidar. Assigning a new value to name does not recalculate earlier assignments.
Assignments begin in column one, with no spaces inside =name:. Use simple names or camelCase; assignment names containing underscores are currently skipped. Underscores work in object keys, host variables, and pipe names. Use $ to read a variable, not to name an assignment.
Dots address nested properties, and numeric path segments address array items:
=user.name: Vidar
=people.0.name: Storm
=first: $people.0.name
Use =,discard: to evaluate a value or run a pipe without storing its result:
=,discard: $message |> log
The host must register log. A discarded result does not change the last assigned result.
Only root assignment keys execute. Nested keys define data. Blank lines do not introduce scopes.
Values
Values include strings, numbers, booleans, null, arrays, and objects. Indentation groups nested values. Comments start with # outside quotes.
=title: Welcome
=count: 42
=price: 19.5
=enabled: true
=selection: null
=names: [Vidar, Storm]
=user:
name: Vidar
roles:
- admin
- author
Arrays and objects also support inline notation:
=user: { name: Vidar, active: true }
=users:
- { name: Vidar, active: true }
- { name: Storm, active: false }
Quote strings that contain syntax characters or could be interpreted as another value type. Quotes do not disable variable expansion or pipes.
=id: '007'
=date: '2026-09-03'
=label: 'Status: ready'
Use |- for multiline text that preserves line breaks, or >- to join lines with spaces. Both omit the final newline.
=message: |-
Hello $user.name,
Your account is ready.
=result: >-
$title
|> up
|> slice 0, 3
Variables
$name reads a variable. $user.name reads a nested property. Strings inside arrays and objects are evaluated recursively.
=user: { name: Vidar, active: true }
=copy: $user
=names: [$user.name, Storm]
=record:
greeting: Hello $user.name!
loud: $user.name |> up
A whole variable expression preserves objects, arrays, booleans, and null. Objects and arrays are returned by reference. Numbers become strings:
=count: 42
=countText: $count # "42"
=active: false
=activeCopy: $active # false
A missing variable evaluates to undefined. An assignment whose final result is undefined leaves both its target and the last assigned result unchanged. A pipe can supply a fallback:
=name: Vidar
=name: $unknown # name remains "Vidar"
=displayName: $unknown |> or guest
In text, missing variables become empty strings. Objects become [object Object], and arrays become comma-separated text. Use a pipe to format structured values. Embedded variable names support letters, digits, underscores, and dotted path segments.
Retrieved values are not evaluated again as source.
Strings
Variables can appear directly in text. Use {{ expression }} to insert the result of a pipe expression or delimit a variable from surrounding text:
=name: Vidar
=count: 2
=greeting: Hello $name!
=summary: '{{ $name }} has {{ $count }} messages'
=loudGreeting: 'Hello {{ $name |> up }}!'
Interpolation inserts strings and numbers; other result types contribute empty text. Put pipes inside the braces. To pipe the entire interpolated string, assign it first, then pipe the variable in a subsequent assignment.
Use \$ and \|> for literal markers. Single-quoted strings preserve backslashes; double-quoted strings require them to be doubled.
=literal: '\$name and \|> stay literal'
Ordinary string whitespace is preserved. A pipe expression trims the whitespace around its initial text value.
Object keys
Object keys can use variables, interpolation, and pipes:
=field: name
=record:
$field: Vidar
'{{ $field |> up }}': Storm
This produces { name: 'Vidar', NAME: 'Storm' }.
Only string and number results become key text; other results become an empty key. Later equal keys replace earlier ones. A key whose source value is an object stays literal, while the object's contents are evaluated recursively.
Pipes
value |> name passes a value to a registered function. Chains run from left to right, awaiting each result. Use a space on each side of |>.
=short: vidar |> up |> slice 0, 3
=names: [Vidar, Storm, Bobby]
=firstTwo: $names |> slice 0, 2
short becomes VID; firstTwo becomes [Vidar, Storm]. Literal text before a pipe is a string, including numeric text. Use a variable to pass an object or array.
Arguments
Commas separate positional arguments. Space-separated key=value pairs form one object argument. Quote argument values containing spaces or commas.
=short: $name |> slice 0, 3
=wrapped: $name |> wrap prefix="Hello " suffix="!"
The second example passes { prefix: 'Hello ', suffix: '!' } to wrap. Commas between named pairs create separate object arguments. Separate named and positional arguments with a comma.
Argument literals recognize numbers and booleans. Other text, including null, stays text. Quoted arguments are strings and still expand variables. Pass null and complex values through variables.
The argument parser treats = as named-argument syntax and |> as a chain separator even inside argument quotes. Pass values containing these sequences through variables. Unterminated quotes and unquoted spaces in named values cause errors.
Host API
Install the package:
npm i weblang
Register each pipe as an object with a handler function:
var weblang = require('weblang')
var pipes = {
up: {
handler: function (ctx, value) {
return value.toUpperCase()
}
}
}
async function main() {
var code = '=message: Hello $name |> up'
var ast = weblang.compile(code, { file: 'hello.w' })
var result = await weblang.run(ast, { vars: { name: 'Vidar' }, pipes })
console.log(result.state.vars.message) // HELLO VIDAR
console.log(result.state.return) // HELLO VIDAR
}
main().catch(console.error)
This handler implements the up pipe. Synchronous and async handlers both receive (ctx, value, ...args) and return the next value. More examples are in spec/lib/pipes.
Compilation and execution
compile(code, { file }) synchronously returns an AST. The host supplies source text; file labels source metadata. Empty or non-string input produces [].
await run(ast, options) executes the program and resolves to { state }.
| Option or result | Meaning |
|---|---|
options.vars |
Initial variable object, used and mutated directly; defaults to {} |
options.pipes |
Registry of pipe handler objects |
| Other options | Available to handlers through ctx.opt |
state.vars |
Variables after execution |
state.last |
Last defined result stored by an assignment |
state.return |
Explicit return value, otherwise state.last; undefined for an empty run |
Handler context
| Property | Use |
|---|---|
ctx.state, ctx.opt |
Shared state and original run options |
ctx.name |
Current pipe name |
ctx.pipe |
Parsed { pipe, args } before expansion |
ctx.args |
Expanded arguments, also passed after value |
ctx.get(object, path) |
Read a nested path |
ctx.set(object, path, value) |
Write a nested path |
ctx.expand(state, value, options, expandOptions) |
Asynchronously expand another value |
To evaluate a retrieved template, call ctx.expand. Pass { pipe: false } as its fourth argument to expand variables without executing pipe chains.
Completion and errors
A handler can set ctx.state.return to select the program's return value and stop execution after the current assignment finishes. Remaining pipes in that assignment still run. Any value except undefined, including false and null, triggers completion.
pipes.finish = {
handler: function (ctx, value) {
ctx.state.return = value
return value
}
}
=result: hello |> finish |> up
=skipped: never executed
This stores HELLO in result and returns hello.
Returning undefined from a handler still feeds the next pipe. Only a final undefined result skips assignment. Returning an error object is ordinary data; throwing an error or rejecting a promise rejects run() immediately.
Compilation checks source structure. Assignment syntax errors include one-based line and column, source, and file (default <memory>). Invalid source data can also throw parser errors. Execution checks pipe arguments and registrations; an unregistered pipe throws missing pipe: name.
Contributing
Contributions are welcome! Visit the GitHub repository to explore the code or submit a pull request. You can also open an issue to report a bug or suggest an improvement. Documentation fixes and examples are welcome too.