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 |> upWith 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.
Use the .w extension for source files, such as app.w.
npm i -g weblang
weblang app.wThe 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 $nameIt prints Hello Vidar.
=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: StormHere, 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.nameUse =,discard: to evaluate a value or run a pipe without storing its result:
=,discard: $message |> logThe 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 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
- authorArrays 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$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 |> upA 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 # falseA 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 guestIn 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.
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 can use variables, interpolation, and pipes:
=field: name
=record:
$field: Vidar
'{{ $field |> up }}': StormThis 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.
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, 2short 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.
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.
Install the package:
npm i weblangRegister 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.
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 |
| 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.
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 executedThis 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.
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.