Origami expressions

A dialect of JavaScript expressions

If you’re familiar with JavaScript, Origami is essentially JavaScript expressions plus paths. It also includes minor adaptations that make it easier to define sites with expressions.

Origami language features are generally optional, but it does enforce one stylistic rule: you have to put spaces around operators like math operators.

Quick reference #

Origami Equivalent JavaScript and notes
src/data.json await fs.readFile("src/data.json")

A file name or path in source code reads that file.

3 / 2 3 / 2 or 3/2

Spaces are required around math operators.

<My File.txt> await fs.readFile("My File.txt")

Use angle brackets if a path contains spaces, parentheses, quotes, etc.

(data.json).name JSON.parse(await fs.readFile("data.json")).name

Reading data out of a file implicitly parses it.

https://example.com await fetch("https://example.com)

A URL can fetch network resources.

"hello" export default "hello";

An Origami file implicitly exports the value of its top-level expression.

fn(a, b) await fn(await a, await b)

All functions and values are potentially async.

{
  a: 1,
  b: a
}
const a = 1;
const object = {
  a,
  b: a
};

An object’s properties can reference its other properties.

{
  a = fn()
}
{
  get a() {
    return fn();
  }
}

Define a getter with an equals sign.

{
  (a): 1
}
Object.defineProperty(object, "a", {
  configurable: true,
  enumerable: false,
  value: 1,
  writable: true
});

A property name in parentheses makes it non-enumerable.

{
  a: 1
  b: 2
}
{
  a: 1,
  b: 2
}

Newlines can be used as separators in arrays, objects, and parameters.

() => { a: 1 } () => ({ a: 1 })

A function can directly return an object without surrounding parentheses.

{
  index.html: "Hi"
}
{
  "index.html": "Hi"
}

Object keys can be file names without having to be quoted.

a → b → c c(b(a))

The pipe operator lets left-to-right order indicate the order of evaluation.

When evaluating Origami expressions on the command line, some additional shell shorthand features are also available.

Identifiers #

As in JavaScript, an Origami identifier is a sequence of characters that can reference a local variable, global variable, function parameter. Additionally, in Origami an identifier can also refer to a local file or folder.

All valid JavaScript identifiers like name or postal_code are valid Origami identifiers — but to allow identifiers to more easily refer to files, Origami identifiers are more permissive than JavaScript’s.

  • Any character allowed in a JavaScript identifier, including characters like digits that are only allowed after the start, are allowed at any position in an Origami identifier. (Specifically, any character can be drawn from the the Unicode ID_Continue class.)
  • Origami identifiers can also include these anywhere: . @ ~
  • Origami identifiers can include these after the first position: ! % & * + - ^ |

Examples of identifiers which are not valid in JavaScript but which are valid in Origami:

index.md
404.html
my-pictures

An identifier can appear on its own or as part of a path.

A standalone sequence of digits and no other characters like 123 is treated as a number; if you need to reference a file or folder whose name is only digits, use angle brackets: <123>. However, a sequence of digits in a path is always treated as an identifier: the path posts/2026 ends with the string identifier “2026”, not the number 2026.

Globals #

Origami expressions can reference the full set of standard JavaScript built-in objects. Origami also adds its own globals:

File name heuristic #

The above definition of identifiers lets you directly include file names in Origami expressions:

Origami.mdHtml(ReadMe.md)

This expression passes the ReadMe.md file to Origami’s builtin mdHtml function.

The period in Origami.mdHtml above is standard JavaScript syntax for property access, but the period in ReadMe.md is just a character in a file name. Origami distinguishes these cases using a heuristic.

To determine how to interpret a period in a sequence like a.b.c:

  1. If there’s a local variable whose entire name is a.b.c, Origami treats the complete name as a reference to that local variable.
  2. Next Origami looks at the part before the first period: a. If that is the name of a local variable, Origami treats the a as a reference to that local variable, and the .b and .c as property access.
  3. Origami then considers whether a is the name of a global. If so, the a references that global variable and the .b and .c are property access.
  4. Otherwise Origami treats the complete name a.b.c as a reference to a local file or folder that will be located using Origami scope.

In the above example of Origami.mdHtml(ReadMe.md):

  • Origami is a global, so Origami.mdHtml gets the mdHtml property of the global Origami object. The value of that is a builtin function.
  • There is no global variable called ReadMe, and assuming there is no local variable with that name, Origami treats ReadMe.md as the name of a local file or folder.

Origami allows the JavaScript math symbols +, -, *, and ~ to appear in a name: e.g., package-lock.json. To invoke a binary operator, add spaces around it; see Operators.

Name conflicts #

One reason the above heuristic generally works is that most JavaScript globals start with or contain uppercase letters, while development projects often use lowercase names for folder and file names. This reduces the chances for name collisions.

That said, the following lowercase JavaScript globals or keywords are available in Origami:

async
await
atob
bota
console
crypto
escape
eval
false
fetch
import
navigator
null
performance
process
true
typeof
undefined
unescape
void

If your project happens to have a folder or file that is one of these names, or has an initial section before the first . that is one of these names, you can surround the path with angle brackets. Example: performance.yaml starts with performance; to avoid conflicting with the performance global, use angle brackets: <performance.yaml>.

Another potential source of name conflicts are file names that begin with the same name as a local:

{
  index.html = index.ori(posts.ori)
  posts/ = Tree.map(markdown/, postPage.ori)
}

Here the posts.ori reference won’t find the expected local file. Instead, it will match the local posts/ variable; see local property references below. Origami will then search that result for a non-existent ori property. To fix this, rename posts.ori or surround it with angle brackets: <posts.ori>.

Note that the following does not produce a conflict:

{
  posts/ = Tree.map(markdown/, posts.ori)
}

When defining an object key like posts/ here, the expression that defines the value will never match the key being defined. (Otherwise this would create an infinite loop.) So posts.ori in this case can’t match the posts/ local variable, and will find the file as expected.

Paths #

A sequence of identifiers separated with / slash characters is treated as a path.

package.json/name

In a path, the first part of the path (here, package.json) will always be treated as a complete identifier. If that name matches a local variable, then the path will be evaluated using that variable as the start. Otherwise Origami assumes the path starts with the name of a local file or folder.

  • A path beginning with ~/ will be taken as a reference to the user’s home directory.
  • If you want to reference an absolute path that starts with a slash, use angle brackets. This avoids conflict with JavaScript’s regular expression syntax: /etc/ is a regular expression, not a path.

Angle brackets #

You can explicitly define a path or URL by enclosing it in < > angle brackets:

<src/data.json>

The first part of the path (here, src) will be resolved using Origami scope; the rest of the path keys will be used to traverse the value to retrieve a final result.

Angle brackets are also useful if your path includes spaces or other characters that aren’t valid in Origami identifiers:

<markdown/My File with Spaces (and Parens).md>

Inside the angle brackets you can use any character except a tab, newline, or >. If you need to include a > in a path, escape it with a \ backslash.

Although Origami can always recognize URLs that start with a protocol, you can also put a URL in angle brackets:

<https://example.com>

Trailing slashes #

Paths in Origami follow a trailing slash convention:

  • If a trailing slash is present, then the value is definitely a traversable subtree.
  • If a trailing slash is not present, the value may or may not be a subtree. That is, a tree isn’t obligated to append slashes to any or all of its keys for traversable subtrees.

In practice, when writing a path for a folder, you have the option to end the path with a trailing slash. If your project includes a folder called “markdown”, both of these paths will find the folder:

markdown
markdown/

You may find the latter form a helpful as a reminder that “markdown/” references a folder.

A trailing slash on a path to a file containing data unpacks the file content into data; see below.

Result of a path #

A path to a file returns the raw contents of the indicated file.

Origami has built-in handlers that can parse the contents of common file types such as JSON and markdown with front matter; see that page for details. This allows you to, for example, obtain your project’s version number from its package.json file via:

(package.json).version

Or you can download a file and extract data from it:

(https://example.com/data.json).users[0].name

You can also use angle brackets for this:

<https://example.com/data.json>.users[0].name

If a file path ends in a trailing slash, Origami will unpack the file content into data. For example, if posts/post1.md is a markdown file, the expression:

posts/post1.md

returns the file content as a Uint8Array. If you want to work with that as text, you can pass that to Origami.string.

If, however, you want to work with that as a document object, you can append a trailing slash:

posts/post1.md/

This returns the content of post1.md as an object, including any front matter data as properties.

Note that most Origami builtin functions that expect a treelike argument will automatically unpack a file or resource (such as Uint8Array or ArrayBuffer) value as long as the path includes a known file extension.

URLs #

If a sequence starts with a scheme (protocol) and a colon, Origami treats it as a URL:

https://example.com

The only characters that cannot be used in a URL are whitespace or characters that end an expression: , ), ], }. If you need to incorporate such a character into a URL, escape it with a \ backslash. Alternatively, place the URL in angle brackets.

Numbers #

Origami supports integers and floating point JavaScript Number values.

Origami does not yet support binary, octal, hexadecimal numeric literals, nor does it support exponential notation. If necessary you can create such numbers with the JavaScript Number() function:

Number('0xf')     // 15
Number('1e2')     // 100

Strings #

Strings with double quotes and single quotes are essentially the same as in JavaScript:

'string'
"string"

You can escape characters with a \ backslash:

'It\'s great'

Templates #

Text templates are quoted in backticks and can contain Origami expressions inside ${ } placeholders. The evaluated expression results will be substituted for those placeholders in the template’s text output.

Expressions inside an Origami template literal can directly return complex values like arrays, objects, or trees. Origami will perform a depth-first traversal of the result, await any Promise values, and concatenate the final values into the string result:

`Hello, ${ { name: 'Alice' } }.`      // "Hello, Alice."

This is different than JavaScript, where the above produces: "Hello, [object Object]."

Among other things, this means that a template can inline the text content a file by name:

$ cat pet.txt
Jiji
$ cat bio.ori
`I have a cat named ${ pet.txt }.`
$ ori bio.ori/
I have a cat named Jiji.

See Templates for more about using templates to generate HTML and other text formats.

You can prefix a template literal with a function name immediately before the leading backtick. This invokes that function with the strings that make up the template’s boilerplate text and a set of values to be substituted in the output — the same function signature as a JavaScript tagged template function. See Tree.indent for an example of a builtin function that you can use in a tagged template.

Object literals #

Origami’s basic syntax for constructing object literals is essentially the same as JavaScript’s: define an object literal with { and } curly braces surrounding key/value pairs:

{
  name: "Alice"
  location: "Honolulu"
}

You can separate key/value pairs with commas, newlines, or both.

Any valid Origami identifier can be used as an object key without being quoted. This means you can define a virtual file with a name like index.html:

{
  index.html: "Welcome to my site!"
}

To define a key that’s not a valid identifier, such as one that includes spaces, put the key in single or double quotes:

{
  "Test File.txt": "Sample text"
}

Shorthand properties #

As a shorthand, you can define a property with just an identifier:

{
  // Include the project's README.md file in this object
  README.md
}

This is shorthand for defining that key with a value that looks up that same key in the current scope:

{
  // Include the project's README.md file in this object
  README.md: README.md
}

This extends to defining a property with a path. The last part of the path becomes the key, so

{
  path/to/file.txt
}

is the same as

{
  file.txt: path/to/file.txt
}

You can also achieve the same result with angle brackets:

{
  <path/to/file.txt>
}

Property getters #

The value in a key/value pair will be evaluated once, when the object is loaded. The following object, for example, evaluates createPage.js() only once:

{
  index.html: createPage.js()
}

Origami lets you define property getters that will be evaluated each time the property is requested, using = equals signs instead of : colons:

{
  index.html = createPage.js()
}

Each time this object is asked for the value of index.html, Origami will invoke createPage.js() and return that result. The above is roughly equivalent to the following JavaScript syntax:

/* JavaScript approximation of the above */
{
  get ["index.html"]() { return createPage(); }
}

Property access #

There are several ways to access the property of an object.

Suppose alice.ori contains:

// alice.ori
{
  name: "Alice"
  location: "Honolulu"
}

You can reference the “name” property of this file in several ways:

  • (alice.ori).name — Put the .name reference after a closing parenthesis
  • alice.ori .name — Put the .name reference after whitespace (this might look a little strange, but is standard JavaScript syntax)
  • alice.ori["name"] — Use the string "name" in square brackets
  • alice.ori/name — Origami treats any treelike object as a tree that can be traversed with path syntax
  • alice.ori("name") — Origami allows any tree to be called like a function.

Local property access #

The expression that defines the value of a property can reference other properties in the same object, or any parent object, by name.

// localRef.ori
{
  a: 1
  b: a
}

This evaluates to:

$ ori localRef.ori/
a: 1
b: 1

This type of local property access is not possible in JavaScript without introducing a separate variable declaration:

// JavaScript equivalent
const a = 1;
const obj = {
  a,
  b: a,
};

Origami will avoid recursive local references. If you try to define a name property whose value expression refers to name, Origami assumes the latter refers to an inherited name property.

// inherited.ori
{
  name: "Alice"
  user: {
    name: `My name is ${ name }`
  }
}

Here the expression ${ name } resolves to the inherited name defined in the parent object.

$ ori inherited.ori
name: Alice
user:
  name: My name is Alice

Non-enumerable properties #

Object keys in parentheses like (x) are not enumerable: they will not be included in a list of the object’s keys. Such properties will generally not be included when the object is copied or displayed.

One use for non-enumerable properties is in calculating a value that will be used in more than one place.

// hidden.ori
{
  (company): "Yoyodyne"
  index.html: `<h1>${ company }</h1>`
  about: {
    index.html: `<h1>About ${ company }</h1>`
  }
}

Here, the declaration of the company property is in parentheses, so it is non-enumerable. It can be referenced by other object properties, but will not be included in the object’s keys. This means it won’t be shown when the object is displayed:

$ ori hidden.ori/
index.html: <h1>Yoyodyne</h1>
about:
  index.html: <h1>About Yoyodyne</h1>

Marking a property as non-enumerable only affects whether it is included in the object’s list of keys; a non-enumerable property is still accessible if one knows the property name:

$ ori hidden.ori/company
Yoyodyne

Keys with trailing slashes #

As noted above, paths can end in trailing slashes to follow Origami’s trailing slash convention.

When defining an object, you have the option of adding a trailing slash to a key whose value is a traversable subtree. This can be a useful way of reminding readers of the code (including yourself later) that the value is a tree.

For example, you might use the Tree.map builtin to convert a collection of markdown files to HTML:

// site.ori
{
  pages/ = Tree.map(markdown/, Origami.mdHtml)
}

Here the trailing slash in pages/ serves as a reminder that the property’s value is a traversable tree of data. If someone asks for this object’s keys, the key for pages/ will include this trailing slash signal:

$ ori keys site.ori
- pages/

Object nesting #

As in JavaScript, object literals can be nested. A common use for this in Origami is to define the overall structure of a website in a .ori file:

// subtree.ori
{
  index.html: `<h1>Home</h1>`
  about: {
    index.html: `<h1>About Us</h1>`
  }
}

When this file is served as a site, the user will be able to browse to about/index.html.

When an object created with an object literal is treated as a tree, any key whose value is another object literal implicitly ends in a trailing slash:

$ ori Tree.keys subtree.ori
- index.html
- about/

Here the builtin Tree.keys function shows a trailing slash on about/ even though a trailing slash isn’t explicitly included in the subtree.ori source code. However, if the object is unpacked and passed to the standard JavaScript Object.keys method, the keys will be returned directly as defined:

$ ori Object.keys subtree.ori/
- index.html
- about

Array literals #

Origami arrays are essentially the same as JavaScript arrays, and are defined with [ ] square brackets:

[1, 2, 3]

As with object literals, you can separate array items with commas, newlines, or both:

[
  1
  2
  3
]

Function calls #

Function calls in Origami look like those in JavaScript:

Math.max(1, 2)

You can call a function defined in a JavaScript file by referencing its file name and then invoking it with parentheses:

greet.js("Alice")

or with an explicit path in angle brackets:

<greet.js>("Alice")

Origami assumes that any function might be asynchronous, so implicitly uses await when calling all functions. For compatibility with JavaScript, Origami allows use of the async and await keywords, but these have no effect on the behavior of the code — they are always implied.

The Origami language runtime itself is written in JavaScript, so types such as numbers, strings, and objects are the same as in JavaScript. E.g., the above example passes an Origami string to a JavaScript function; inside that function, the supplied argument will be a regular JavaScript string.

Origami does not yet support ... spreads in function calls.

Trees as functions #

Origami’s AsyncTree interface lets you treat a wide variety of treelike structures as hierarchical trees. You can use function syntax to obtain a value from the tree; this invokes the tree’s get method.

This means you can use function call syntax to retrieve a value from a data structure, a folder, etc. If you have a data file:

# capitals.yaml
Japan: Tokyo
Turkey: Ankara
Australia: Canberra
Spain: Madrid

you can extract a value from it with function syntax:

$ ori "capitals.yaml('Spain')"
Madrid

One use for this is to programmatically look up values: if a variable holds the name of a key, you can call the tree as a function and pass the variable as the argument.

Functions as trees #

Conversely, Origami lets you treat a function as a treelike object. This is true for arrow functions, Origami builtin functions, and functions exported by JavaScript modules.

This means you can traverse a function using path syntax, which can be convenient when using the Origami CLI to evaluate expressions in the shell. A shell will typically try to handle parentheses, and so disallow a function call with unquoted parentheses. But a shell will generally leave slashes untouched, allowing you to invoke a function and pass an argument.

// uppercase.js
export default (x) => x.toString().toUpperCase();
$ ori uppercase.js/hello
HELLO

Arrow functions #

An Origami expression can define a type of unnamed function called an arrow function.

You can define arrow functions using => and any number of parameters:

(parameter1, parameter2, parameter3, …) => expression

That expression will not be evaluated immediately, only later when the function is invoked.

For example, the Tree.map builtin function can apply another function to a tree’s values and/or keys. To define a function that will be evaluated in the context of each tree value, you can use an arrow function:

$ ori "Tree.map(['a', 'b', 'c'], (letter) => letter.toUpperCase())"
- A
- B
- C

Origami arrow function syntax does not yet support JavaScript’s syntax for default parameters, rest parameters, or parameter destructuring.

Operators #

Origami closely follows JavaScript operator precedence. Origami introduces some operators of its own, listed here alongside the standard JavaScript operators in order from highest to lowest precedence.

Operator Example Associativity
Group (x) n/a
Path x/y/z left-to-right
Member access x.y left-to-right
Computed member access x[y] n/a
Function call x(y) n/a
import import(x) n/a
Tagged template x`y` n/a
Logical NOT !x n/a
Bitwise NOT ~x n/a
Unary plus +x n/a
Unary minus -x n/a
typeof typeof x n/a
void void x n/a
await await x n/a
Exponentiation x ** y right-to-left
Multiplication x * y left-to-right
Division x / y left-to-right
Remainder x % y left-to-right
Addition x + y left-to-right
Subtraction x - y left-to-right
Left shift x << y left-to-right
Right shift x >> y left-to-right
Unsigned right shift x >>> y left-to-right
Less than x < y left-to-right
Less than or equal x <= y left-to-right
Greater than x > y left-to-right
Greater than or equal x >= y left-to-right
Equality x == y left-to-right
Inequality x != y left-to-right
Strict equality x === y left-to-right
Strict inequality x !== y left-to-right
Logical AND x && y left-to-right
Logical OR x || y left-to-right
Nullish coalescing x ?? y left-to-right
Conditional (ternary) x ? y : z right-to-left
Arrow (x) => y right-to-left
Implicit parentheses x y right-to-left
Shorthand function =x right-to-left
Pipe x -> y left-to-right
Spread ...x n/a
Comma x, y left-to-right

An operator’s precedence determines how the Origami parser handles expressions that have more than one possible interpretation.

Example: a -> b => c could be interpreted as a -> (b => c) or (a -> b) => c. Origami uses the former interpretation, because the arrow operator has a higher precedence than Origami’s pipe operator.

Origami requires spaces around binary operators:

  • x + y
  • x - y
  • x * y
  • x / y

Without the spaces, Origami interprets x/y as a path. Similarly, operators without surrounding spaces are treated as part of an identifier: package-lock.json is an identifier, not a subtraction.

Spaces are not required around unary operators: -foo negates the value of foo. If you need to reference a local file that starts with a hyphen, put the name in angle brackets: <-foo>.

Some addition usage notes:

  • Instead of calling import, you can generally use a path instead.
  • The await operator is implied in Origami so can always be omitted.

Origami does not yet support JavaScript’s optional chaining operator: x?.y.

Spread operator #

You can use ... three periods to merge arrays and objects.

$ ori tree1.yaml
a: The letter A
b: The letter B
c: This will be overwritten when merged
$ ori tree2.yaml
c: The letter C
d: The letter D
e: The letter E
$ ori { ...tree1.yaml, ...tree2.yaml }
a: The letter A
b: The letter B
c: The letter C
d: The letter D
e: The letter E

As in JavaScript, the last definition of a given key will be used. While both files above define a value for c, it’s the second file whose value of c is used.

In an .ori file, you can use this to merge a folder into an object that also defines individual files.

{
  index.html: "Hello!"

  // Merge in everything in the `styles` folder
  ...styles
}

The spread operator performs a shallow merge. You can also perform a shallow merge with the built-in Tree.merge function. For a deep merge, see Tree.deepMerge.

Pipe operator #

Origami has a pipe operator (which can also be written ->) for representing a sequence of function calls. This can be used to avoid deep call nesting.

These deeply-nested function calls:

three(two(one(value)))

can be rewritten using the pipe operator:

value  one  two  three

This can be useful when applying multiple transformations of data. Suppose an index page is generated from markdown and then placed inside a template:

{
  index.html = template.ori(Origami.mdHtml(index.md))
}

You can rewrite the above using the pipe operator so that the flow of data reads proceeds from left to right:

{
  index.html = index.md  Origami.mdHtml  template.ori
}

This may make the flow of data easier to see.

The pipe operator passes the value from the left side of the operator to the function on the right side. If you need to provide additional arguments to that function, wrap the call in a arrow function that accepts a single argument and then calls your desired function with the additional arguments.

Example: these nested calls to Origami.mdHtml and Tree.sort both take multiple arguments:

Tree.sort(Tree.map(markdown/, Origami.mdHtml), { compare: Origami.naturalOrder })

This can be rewritten with the pipe operator and arrow functions:

markdown/
 (mdFiles) => Tree.map(mdFiles, Origami.mdHtml)
 (htmlFiles) => Tree.sort(htmlFiles, { compare: Origami.naturalOrder })

Grouping #

You can group expressions with ( ) parentheses.

Comments #

Just like JavaScript, line comments start with // double slashes and extend to the end of the line:

// This is a line comment

One difference is that, in a URL or path, Origami interprets consecutive double slashes as part of the path, not a comment.

https://example.com/
path/with/consecutive//slashes

Those lines contain double slashes, but neither contains a comment.

Block comments are enclosed by /* */

/*
Block comment
*/

Implied exports #

Evaluating any Origami file with the .ori extension returns an evaluation of the file’s top-level expression — unlike JavaScript, there is no need to explicitly export a value.

This message.ori file evaluates to a string:

// message.ori
"This file exports this message."

It is equivalent to this JavaScript:

// message.js
export default "This file exports this message.";

Shell shorthand syntax #

To accommodate the use of Origami on the command line, the Origami CLI supports some additional shorthand syntax. The shorthands are not supported inside Origami .ori files.

Unsupported JavaScript features #

As an expression language, Origami does not include any of JavaScript’s control structures like for or while loops. For the same reason, Origami doesn’t support JavaScript operators that have side effects:

  • Postfix and prefix operators ++, --
  • Assignment operators =, +=, -=, etc.
  • delete operator

Additionally, Origami does not currently support these features from JavaScript:

  • binary, octal, hexadecimal numeric literals, or exponential notation (see Numbers)
  • default parameters, rest parameters, parameter destructuring (see Arrow functions)
  • ... spread operator in function calls
  • ?. optional chaining operator (see Operators)
  • defining an object key with a computed property