Terrace JavaScript Documentation

Documentation is available for the following languages:

Getting Started

Install Terrace using NPM:

# NPM (https://npmjs.com)
$ npm install @terrace-lang/js

# PNPM (https://pnpm.io/)
$ pnpm install @terrace-lang/js

# Yarn (https://yarnpkg.com/)
$ yarn add @terrace-lang/js

Core API

Note: The Core API uses C-style conventions to optimize memory management and improve portability to other environments and languages. It is unwieldy and does not follow JavaScript best practices.

For most projects you'll want to use the Document API instead. It provides an ergonomic wrapper around the Core API and lets you focus on parsing your documents.

LineData

// Type Definition
// Holds the parsed information from each line.
type LineData = {
  // Which character is being used for indentation. Avoids having to specify it on each parseLine call.
  indent: string;
  // How many indent characters are present in the current line before the first non-indent character.
  level: number;
  // The number of characters before the start of the line's "head" section.
  // (Normally the same as `level`)
  offsetHead: number;
  // The number of characters before the start of the line's "tail" section.
  offsetTail: number;
}

createLineData()

Parameter Type Description
indent string The character used for indentation in the document. Only a single character is permitted.
@returns LineData A LineData instance with the specified indent character and all other values initialized to 0.

Initialize a LineData instance with default values to pass to parseLine().

// Type Definition
function createLineData(indent: string = ' '): LineData

// Import Path
import { createLineData } from '@terrace-lang/js/parser'

// Usage
const lineData = createLineData(' ')
console.dir(lineData)
// { indent: ' ', level: 0, offsetHead: 0, offsetTail: 0 }
// Use the same lineData object for all calls to parseLine in the same document.

parseLine()

Parameter Type Description
line string A string containing a line to parse. Shouldn't end with a newline.
lineData LineData A LineData object to store information about the current line, from createLineData().
Mutated in-place!

Core Terrace parser function, sets level, offsetHead, and offsetTail in a LineData object based on the passed line. Note that this is a C-style function, lineData is treated as a reference and mutated in-place.

// Type Definition
function parseLine(lineData: LineData): LineData

// Import Path
import { createLineData, parseLine } from '@terrace-lang/js/parser'

// Usage
const lineData = createLineData(' ')
parseLine('title Example Title', lineData)
console.dir(lineData)
// { indent: ' ', level: 0, offsetHead: 0, offsetTail: 5 }

Document API

useDocument()

Parameter Type Description
reader Reader When called, resolves to a string containing the next line in the document.
indent string The character used for indentation in the document. Only a single character is permitted.
@returns Document A set of convenience functions for iterating through and parsing a document line by line.

Provides a simple set of convenience functions around parseLine for more ergonomic parsing of Terrace documents.

// Type Definition
function useDocument (reader: Reader, indent: string = ' '): Document

// Import Path
import { useDocument } from '@terrace-lang/js/document'

Document

Container for a handful of convenience functions for parsing documents. Obtained from useDocument() above

// Type Definition
type Document = {
  next: (levelScope?: number) => Promise<boolean>
  level: () => number,
  line: (startOffset?: number) => string,
  head: () => string,
  tail: () => string,
  match: (matchHead: string) => boolean
}

Document.next()

Parameter Type Description
levelScope number = -1 If specified, next() will return false when it encounters a line with a level at or below levelScope
@returns Promise Returns true after parsing a line, or false if the document has ended or a line at or below levelScope has been encountered.

Advances the current position in the terrace document and populates lineData with the parsed information from that line.

Returns true after parsing the next line, or false upon reaching the end of the document. If the levelScope parameter is provided, next() will return false when it encounters a line with a level at or below levelScope. This allows you to iterate through subsections of a document.

If a lower-level line was encountered, the following call to next() will repeat this line again. This allows a child loop to look forward, determine that the next line will be outside its purview, and return control to the calling loop transparently without additional logic.

Intended to be used inside a while loop to parse a section of a Terrace document.

// Type Definition
next: (levelScope?: number) => Promise<boolean>

// Import Path
import { useDocument } from '@terrace-lang/js/document'

// Usage
const { next } = useDocument(...)
while (await next()) {
  // Do something with each line.
}

Document.level()

Parameter Type Description
@returns number The indent level of the current line

Returns the number of indent characters of the current line.

Given the following document, level() would return 0, 1, 2, and 5 respectively for each line.

block
  block
  block
      block
// Type Definition
level: () => number

// Usage
import { useDocument } from '@terrace-lang/js/document'
const { level } = useDocument(...)

Document.line()

Parameter Type Description
levelScope startOffset = level() How many indent characters to skip before outputting the line contents. Defaults to the current indent level
@returns string The line contents starting from startOffset

Get a string with the current line contents. Skips all indent characters by default, but this can be configured with startOffset

Given the following document

root
    sub-line
  • Calling line() on the second line returns "sub-line", trimming off the leading indent characters.
  • Calling line(0) however, returns "    sub-line", with all four leading spaces.

startOffset is primarily used for parsing blocks that have literal indented multi-line text, such as markdown.

// Type Definition
line: (startOffset?: number) => string

// Usage
import { useDocument } from '@terrace-lang/js/document'
const { line } = useDocument(...)

Document.head()

Parameter Type Description
@returns string The head portion (first word) of a line

Get the first "word" of a line, starting from the first non-indent character to the first space or end of the line. Often used for deciding how to parse a block.

Terrace DSLs do not need to use head-tail line structure, but support for them is built into the parser

Given the following line, head() returns "title"

title An Important Document
// Type Definition
head: () => string

// Usage
import { useDocument } from '@terrace-lang/js/document'
const { head } = useDocument(...)

Document.tail()

Parameter Type Description
@returns string The remainder of the line following the head() portion, with no leading space

Get all text following the first "word" of a line, starting from the first character after the space at the end of head()

Terrace DSLs do not need to use head-tail line structure, but support for them is built into the parser

Given the following line, tail() returns "An Important Document"

title An Important Document
// Type Definition
tail: () => string

// Usage
import { useDocument } from '@terrace-lang/js/document'
const { tail } = useDocument(...)

Document.match()

Parameter Type Description
matchValue string A string to check against head() for equality
@returns boolean Whether the current head() matches the passed value

Quickly check if the current line head matches a specified value

Shorthand for matchValue === head()

Given the following line

title An Important Document
  • match('title') returns true
  • match('somethingElse) returns false
// Type Definition
match: (matchValue: string) => boolean

// Usage
import { useDocument } from '@terrace-lang/js/document'
const { match } = useDocument(...)

Reader API

The Document API requires Reader functions to iterate through lines in a document. A reader function simply returns a string (or a promise resolving to a string). Each time it is called, it returns the next line from whichever source it is pulling them.

Terrace provides a few built-in readers, but you are welcome to build your own instead.

Reader

Any function (async included) that returns the next line in a document when called and null when the end of the document has been reached.

// Type Definition
type Reader = () => string|null|Promise<string|null>

createStringReader()

Parameter Type Description
source string|string[] The lines to iterate over, as a multiline string or an array of line strings.
index number Optional - which line to start from.
@returns Reader A reader function that returns each line from the source document when called sequentially.

Get a simple Reader function that always returns the next line from a mutliline string or an array of line strings.

// Type Definition
function createStringReader(source: string|string[], index: number = 0): Reader

// Import Path
import { createStringReader } from '@terrace-lang/js/readers/js-string'

Usage

import { createStringReader, useDocument } from '@terrace-lang/js'

// Create a string reader with two lines
const reader = createStringReader('title Example Title\n line2')
// Also permitted:
// const reader = createStringReader(['title Example Title', ' line 2'])

const { next, level, line } = useDocument(reader)
await next()
console.log(level(), line())
// 0 title Example Title
await next()
console.log(level(), line())
// 1 line 2

createFileReader()

Parameter Type Description
path string A path to the file to read.
@returns Reader A reader function that returns each line from the file when called sequentially

Note: Only available in Node.js environments.
Get a Reader function that returns the next line from the specified file when called sequentially.

// Type Definition
function createFileReader(path: string): Reader

// Import Path
import { createFileReader } from '@terrace-lang/js/readers/node-readline'

Usage

main.js
import { createFileReader, useDocument } from '@terrace-lang/js'

// Read the file ./example.tce
const { next, level, line } = useDocument(createFileReader('./example.tce'))
await next()
console.log(level(), line())
// 0 title Example Title
await next()
console.log(level(), line())
// 1 line 2
example.tce
title Example Title
  line 2

createStdinReader()

Parameter Type Description
@returns Reader A reader function that returns each line from stdin when called sequentially

Note: Only available in Node.js environments.
Get a Reader function that returns the next line from standard input when called sequentially. Does not block stdin to wait for input. If no input is present it returns null immediately.

// Type Definition
function createStdinReader(): Reader

// Import Path
import { createStdinReader } from '@terrace-lang/js/readers/node-readline'

Usage

main.js
import { createStdinReader, useDocument } from '@terrace-lang/js'

// Read the contents of standard input
const { next, level, line } = useDocument(createStdinReader())

while(await next()) {
  console.log(level(), line())
  // See `shell` panel above for output
}
example.tce
title Example Title
  line 2
shell
# Run main.js with the contents of example.tce piped to stdin
$ cat example.tce > node ./main.js
0 title Example Title
1 line 2

createStreamReader()

Parameter Type Description
stream NodeJS.ReadStream fs.ReadStream
@returns Reader A reader function that returns each line from stdin when called sequentially

Note: Only available in Node.js environments.
Get a Reader function that always returns the next line from a passed read stream when called sequentially.

// Type Definition
function createStreamReader(): Reader

// Import Path
import { createStreamReader } from '@terrace-lang/js/readers/node-readline'

Usage

main.js
import fs from 'node:fs'
import { createStreamReader, useDocument } from '@terrace-lang/js'

// Read the file ./example.tce - equivalent to the `createFileReader` example above.
const reader = createStreamReader(fs.createReadStream('./example.tce'))
const { next, level, line } = useDocument(reader)
await next()
console.log(level(), line())
// 0 title Example Title
await next()
console.log(level(), line())
// 1 line 2
example.tce
title Example Title
  line 2

Recipes

Read object properties

Read known properties from a Terrace block and write them to an object.

// Provides simple convenience functions over the core parser
// CommonJS: const { useDocument } = require('@terrace-lang/js/document')
import { useDocument } from '@terrace-lang/js/document'
// A helper for iterating over a string line-by-line
// CommonJS: const { createStringReader } = require('@terrace-lang/js/readers/js-string')
import { createStringReader } from '@terrace-lang/js/readers/js-string'

const input = `
object
  string_property An example string
  numeric_property 4
`

const output = {
  string_property: null,
  numeric_property: null
}

// useDocument returns convenience functions
const { next, level, head, tail, match } = useDocument(createStringReader(input))

// next() parses the next line in the document
while (await next()) {
  // match('object') is equivalent to head() === 'object'
  // Essentially: "If the current line starts with 'object'"
  if (match('object')) {
    const objectLevel = level()
    // When we call next with a parent level it,
    // only iterates over lines inside the parent block
    while (await next(objectLevel)) {
      // tail() returns the part of the current line after the first space
      if (match('string_property')) output.string_property = tail()
      // parseFloat() here the string tail() to a numeric float value
      if (match('numeric_property')) output.numeric_property = parseFloat(tail())
    }
  }
}

console.dir(output)
// { string_property: 'An example string', numeric_property: 4 }

Read all properties as strings from a Terrace block and write them to an object.

// Provides simple convenience functions over the core parser
// CommonJS: const { useDocument } = require('@terrace-lang/js/document')
import { useDocument } from '@terrace-lang/js/document'
// A helper for iterating over a string line-by-line
// CommonJS: const { createStringReader } = require('@terrace-lang/js/readers/js-string')
import { createStringReader } from '@terrace-lang/js/readers/js-string'

const input = `
object
  property1 Value 1
  property2 Value 2
  random_property igazi3ii4quaC5OdoB5quohnah1beeNg
`

const output = {}

// useDocument returns convenience functions
const { next, level, head, tail, match } = useDocument(createStringReader(input))

// next() parses the next line in the document
while (await next()) {
  // match('object') is equivalent to head() === 'object'
  // Essentially: "If the current line starts with 'object'"
  if (match('object')) {
    const objectLevel = level()
    // When we call next with a parent level,
    // it only iterates over lines inside the parent block
    while (await next(objectLevel)) {
      // Skip empty lines
      if (!line()) continue
      // Add any properties to the object as strings using the
      // line head() as the key and tail() as the value
      output[head()] = tail()
    }
  }
}

console.dir(output)
// { property1: 'Value 1', property2: 'Value 2', random_property: 'igazi3ii4quaC5OdoB5quohnah1beeNg' }

Contributing

Maintained by the Terrace Team. Find an issue? Let us know!

Site contents licensed under the CC BY 3.0 license
All code examples licensed under the MIT license