Origami language syntax

The Origami language is relatively small and focused on:

  • Defining tree structures like that of a website’s pages and resources
  • Traversing paths into trees of data or files
  • Creating text content using templates
  • Invoking functions defined in other languages like JavaScript

Origami expression syntax generally follows JavaScript, with some differences such as: URLs, file paths, and file names are valid references; and some shorthands make it easier to type expressions in the shell when using the CLI.

Microsoft Visual Studio Code users can install the Origami language extension for syntax highlighting.

Strings

Strings use single or double quotes:

'string'
"string"

You can escape characters with a \ backslash:

'It\'s great'

Since command lines often process both single and double quotes, Origami also supports the use of French-style « and » guillemet quote characters:

$ ori «Hello»
Hello

On macOS, you can type the « character with Option+Backslash, and the » character with Option+Shift+Backslash. On Windows, you can type Alt+0171 and Alt+0187, respectively.

Numbers

On their own, integers or floating-point numbers are treated as JavaScript Number values.

42
3.14159

Numbers that appear in paths are treated as strings:

years/2023

searches in the years tree for the key “2023”.

If you have a string that you want to convert to a number, use the Number builtin function. Like any function, you can call it with parentheses or a slash:

Number("123")   // the number 123
Number/123      // the number 123

References

Unquoted character sequences will be evaluated in the current scope to find, for example, a file or other tree value with the indicated name.

$ ls
Hello.md
$ cat Hello.md
Hello, world.
$ ori Hello.md
Hello, world.

In the last command above, Hello.md is evaluated as a reference. In this case, it finds the local file, Hello.md, so ori displays the contents of that file.

Unlike JavaScript identifiers, it is legal to include a . period in a reference. Spaces and the following characters

(){}[]<>?!=,/:`"'«»\→⇒…

must be escaped with a \ backslash or in quotes.

Example: If you have a file with a space like foo bar, you could reference it with

foo\ bar

If you know the file’s in a particular folder, such as the current directory, you could also use:

./("foo bar")

Or you can search the current scope with the scope: protocol:

scope:("foo bar")

A reference that’s entirely numeric digits also requires special handling or it will be treated as a number. For example, you might have folders organized by year with names like 2025. To force Origami to treat that as a reference, you can write:

scope:2025

Object literals

Object literals are created with { and } curly braces, and contain key/value pairs:

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

You can separate key/value pairs with commas, newlines, or both. Keys should not be quoted.

As with references (above), you can use periods in keys. Escape any special characters like spaces:

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

You can also put keys in single or double quotes:

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

As a shorthand, you can define a property with just a key. The key will be used as a reference (above) that will look up that key in the current scope.

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

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

{
  name: getName()
}

Object properties can reference other local properties

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 reference is not possible in languages like JavaScript.

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} will resolve to the inherited name defined in the parent object.

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

Object property getters

If you’d like a value to be calculated every time it’s requested, you can create a property getter. You create a property getter in Origami by using an = equals sign to define the property instead of a : colon.

{
  index.html = greet.js()
}

This .ori expression defines a getter called index.html. Each time the object is asked for index.html, it will invoke the JavaScript function exported by greet.js.

Object nesting

Objects can be deeply nested.

One use for this is to define the overall structure of a website in a .ori file:

{
  // Include the entire `assets` folder
  assets

  about: {
    // Generate the index page for the "About" area
    index.html = about.ori()
  }
}

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

Array literals

Arrays use [ ] square brackets.

[1, 2, 3]

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

[
  1
  2
  3
]

Spread operator

You can use ... three periods or the single ellipsis character 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

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 built-in merge function performs this same operation as a function.

Function calls

You can invoke a function with parentheses:

fn(arg)

The arguments to a function will be evaluated, then the function will be invoked:

$ ori sample.txt
This is a text file.
$ ori uppercase.js
export default (x) => x.toString().toUpperCase();
$ ori "uppercase.js(sample.txt)"
THIS IS A TEXT FILE.

To make it easier for you to invoke functions in the command line, Origami expressions also let you use implicit parentheses for function calls. For example, the above can also be written as:

$ ori uppercase.js sample.txt
THIS IS A TEXT FILE.

In some situations, you can also avoid the need for parentheses by using a / slash; see below.

Paths

Paths are a sequence of string keys separated with / slashes.

tree/with/path/to/something

The head of the path, like tree here, is resolved as a reference. If that returns a treelike object, it will be traversed using the remaining keys “with”, “path”, “to”, and “something”. Treelike objects include object literals and hierarchical data in JSON and YAML files:

$ ori greetings.yaml
Alice: Hello, Alice.
Bob: Hello, Bob.
Carol: Hello, Carol.
$ ori greetings.yaml/Alice
Hello, Alice.

In Origami, functions are a type of tree, so a function can also be invoked with slash syntax:

$ ori greet.js
export default (name = "world") => `Hello, ${name}.`;
$ ori greet.js/David
Hello, David.

A function call using a slash like this lets you avoid having to quote parentheses in the command line.

If the greet.js function here is invoked with a trailing slash, that invokes the function with an undefined value for the name parameter. In this example, greet then uses a default name:

$ ori greet.js/
Hello, world.

File paths

Paths that start with a leading / slash refer to absolute paths in the filesystem:

/Users/alice/example.txt

Similarly, paths that start with a leading ./ refer to relative paths, and paths that start with ../ refer to the current parent folder.

Paths can traverse into files of known file types like .json files:

$ ori /Users/alice/myProject/package.json
{
  "name": "Test project",
  "type": "module"
}
$ ori /Users/alice/myProject/package.json/name
Test project

You can reference folder or file by name without any special leading character; such names will be resolved in scope. Example: if a project contains a folder called src, then the following are equivalent:

$ ori ./src
$ ori src

Inside an Origami program, if you have a local variable that has the same name as a folder in scope, and you want to explicitly reference the folder instead of the variable, you can use the files: protocol.

Namespaces

Origami organizes its built-in functions and objects into namespaces: groups of things with a name. These names end in a colon.

Some namespaces like https: act like protocols in URLs:

https://example.com

The result of this URL will be the contents of the data at the indicated internet location.

In addition to https: and http:, Origami has some custom protocols like files:.

Other namespaces like dev: and tree: act as containers for functions and objects built into Origami. For example, the tree: namespace contains a function called tree:plain that converts a tree (of markdown files, say) to a plain JavaScript object.

tree:plain(markdown)

Shorthand for builtin functions

For faster typing in the command line and for more concise code generally, Origami allows built-in functions like tree:plain to be called in a shorthand form like plain that omits the namespace.

This means the following commands are equivalent:

$ ori tree:copy src/site.ori, tree:clear ./build
$ ori copy src/site.ori, clear ./build

To distinguish between builtin function names and your own function names, the Origami parser enforces the following rule for the names of functions: if a function name contains only letters and numbers, it is taken to refer to a builtin. This rule applies to the names of functions:

  • invoked with parentheses
  • invoked with spaces that represent implicit parentheses
  • invoked as a tagged template (see template literals, below)

Examples:

  • map(markdown, page.ori)map is taken as a shorthand for the tree:map because map is only letters and appears before parentheses.
  • yaml package.jsonyaml is a shorthand (for origami:yaml) because it’s only letters and appears in the function position. The parentheses are implicit; it’s the same as yaml(package.json).
  • uppercase.js(sample.txt)uppercase.js is not a builtin name because it contains a period.
  • mdHtml("Hello")mdHtml is a shorthand (for text:mdHtml).
  • mdHtml/HellomdHtml isn’t immediately assumed to be a shorthand because it’s being called with slash syntax (see Paths, above). The reference mdHtml will be resolved in scope. If a folder exists called “mdHtml”, the reference will resolve to that folder, not the builtin, but if there is nothing else in scope called “mdHtml”, then mdHtml will ultimately resolve to the builtin.

In the last example, using a slash path prevented mdHtml from being immediately taken as the name of a builtin. In some cases you may have a local variable that contains a function, and the variable’s name is only letters and numbers, such that it will be assumed to be a shorthand for a builtin function. If you want the name to reference the local variable in a function call, append a / slash to the name:

// Call `fn` as a function even though its name looks like a shorthand
(fn) => fn/()

Alternatively, place parentheses around the name:

// Another way to call `fn` as a function
(fn) => (fn)()

Both techniques cause the fn reference to be treated as a regular scope reference. In both cases the reference will find the local fn parameter, which can then be invoked as a function.

Template literals

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.

$ cat pet.txt
Fluffy
$ cat sample.ori
`I have a pet named ${ pet.txt }.`
$ ori sample.ori/
I have a pet named Fluffy.

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. This function signature is compatible with JavaScript tagged template functions. See indent for an example of a builtin function that you can use in a tagged template.

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

Grouping

You can group expressions with ( ) parentheses. Command shells generally interpret parentheses, so you will need to escape them with backslashes or quote the expression you want ori to evaluate.

Lambdas (unnamed functions)

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

You can create the simplest form of a lambda function with an = equals sign:

=expression

This expression will not be evaluated immediately, but only later when explicitly invoked.

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

$ cat letters.json
{
  "a": "The letter A",
  "b": "The letter B",
  "c": "The letter C"
}
$ cat uppercase.js
export default (x) => x.toString().toUpperCase();
$ ori "map(letters.json, =uppercase.js(_))"
a: THE LETTER A
b: THE LETTER B
c: THE LETTER C

The _ underscore above refers to the value being mapped, so =uppercase.js(_) will convert the value to uppercase.

You can also define lambda functions with an expanded syntax using a “=>” (or the Unicode ⇒ Rightwards Double Arrow) that allows for multiple named parameters:

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

The map function shown above passes the mapping function the value and key being mapped — in that order — as arguments, so the above example can be rewritten:

$ ori "map(letters.json, (description, letter) => uppercase.js(description))"
a: THE LETTER A
b: THE LETTER B
c: THE LETTER C

In this case, since the letter argument isn’t used, it can be omitted:

$ ori "map(letters.json, (description) => uppercase.js(description))"
a: THE LETTER A
b: THE LETTER B
c: THE LETTER C

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(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  mdHtml  template.ori
}

This may make the flow of data easier to see.

Comments

Line comments start with // double slashes and extend to the end of the line:

// This is a line comment

Note: In a URL or file path (see above), Origami interprets consecutive double slashes as part of the path and not a comment.

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

Block comments are enclosed by /* */

/*

Block comment

*/

Instantiating classes

For interoperability with JavaScript classes, Origami supports a new: syntax for creating new class instances.

If the JavaScript file User.js exports a User class, then

new:User.js("David")

is equivalent to the JavaScript new User("David").