Document most of the JS 'Document' API

This commit is contained in:
Joshua Bemenderfer 2023-02-15 22:43:58 -05:00
parent c1ac40905d
commit 107a164ec8
4 changed files with 274 additions and 29 deletions

View File

@ -19,7 +19,7 @@ Section light
Markdown Markdown
Documentation is available for the following languages: Documentation is available for the following languages:
- [C](/docs/c/) - 10% Complete - [C](/docs/c/) - 10% Complete
- [JavaScript](/docs/javascript/) - 20% Complete - [JavaScript](/docs/javascript/) - 50% Complete
- [Python](/docs/python/) - 0% Complete - [Python](/docs/python/) - 0% Complete
Heading 2 Getting Started Heading 2 Getting Started

View File

@ -19,7 +19,7 @@ Section light
Markdown Markdown
Documentation is available for the following languages: Documentation is available for the following languages:
- [C](/docs/c/) - 10% Complete - [C](/docs/c/) - 10% Complete
- [JavaScript](/docs/javascript/) - 20% Complete - [JavaScript](/docs/javascript/) - 50% Complete
- [Python](/docs/python/) - 0% Complete - [Python](/docs/python/) - 0% Complete
Heading 2 Getting Started Heading 2 Getting Started
@ -129,10 +129,13 @@ Section light
Heading 2 Core API Heading 2 Core API
class mt-12 class mt-12
Markdown
**TODO:** Document
Heading 3 LineData Heading 3 LineData
class my-6 class my-6
CodeBlock typescript CodeBlock typescript
// Type Definition
type LineData = { type LineData = {
line: string; line: string;
indent: string; indent: string;
@ -160,16 +163,30 @@ Section light
Heading 3 useDocument() Heading 3 useDocument()
class my-6 class my-6
Markdown
| Parameter | Type | Description
| -------------- | --------------------- | -----------------------------------------------------------------------
| reader | [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](#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.
CodeBlock typescript CodeBlock typescript
// Type Definition
function useDocument (reader: Reader, indent: string = ' '): Document function useDocument (reader: Reader, indent: string = ' '): Document
CodeBlock javascript
// Import Path
import { useDocument } from '@terrace-lang/js/document' import { useDocument } from '@terrace-lang/js/document'
Heading 3 Document Heading 3 Document
class my-6 class my-6
Markdown
Container for a handful of convenience functions for parsing documents.
Obtained from [useDocument()](#usedocument) above
CodeBlock typescript CodeBlock typescript
// Type Definition
type Document = { type Document = {
next: (startLevel?: number) => Promise<boolean> next: (levelScope?: number) => Promise<boolean>
level: () => number, level: () => number,
line: (startOffset?: number) => string, line: (startOffset?: number) => string,
head: () => string, head: () => string,
@ -179,79 +196,198 @@ Section light
Heading 3 Document.next() Heading 3 Document.next()
class my-6 class my-6
Markdown
| 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<boolean> | 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.
CodeBlock typescript CodeBlock typescript
next: (startLevel?: number) => Promise<boolean> // Type Definition
CodeBlock javascript next: (levelScope?: number) => Promise<boolean>
// Import Path
import { useDocument } from '@terrace-lang/js/document' import { useDocument } from '@terrace-lang/js/document'
// Usage
const { next } = useDocument(...) const { next } = useDocument(...)
while (await next()) {
// Do something with each line.
}
Heading 3 Document.level() Heading 3 Document.level()
class my-6 class my-6
Markdown
| 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.
CodeBlock terrace
block
block
block
block
CodeBlock typescript CodeBlock typescript
// Type Definition
level: () => number level: () => number
CodeBlock javascript
// Usage
import { useDocument } from '@terrace-lang/js/document' import { useDocument } from '@terrace-lang/js/document'
const { level } = useDocument(...) const { level } = useDocument(...)
Heading 3 Document.line() Heading 3 Document.line()
class my-6 class my-6
Markdown
| Parameter | Type | Description
| -------------- | --------------------- | -----------------------------------------------------------------------
| levelScope | startOffset = [level()](#document-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
CodeBlock terrace
root
sub-line
Markdown
- Calling `line()` on the second line returns "sub-line", trimming off the leading indent characters.
- Calling `line(0)` however, returns "&nbsp;&nbsp;&nbsp;&nbsp;sub-line", with all four leading spaces.
`startOffset` is primarily used for parsing blocks that have literal indented multi-line text, such as markdown.
CodeBlock typescript CodeBlock typescript
// Type Definition
line: (startOffset?: number) => string line: (startOffset?: number) => string
CodeBlock javascript
// Usage
import { useDocument } from '@terrace-lang/js/document' import { useDocument } from '@terrace-lang/js/document'
const { line } = useDocument(...) const { line } = useDocument(...)
Heading 3 Document.head() Heading 3 Document.head()
class my-6 class my-6
Markdown
| 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()](#document-head) returns "title"
CodeBlock terrace
title An Important Document
CodeBlock typescript CodeBlock typescript
// Type Definition
head: () => string head: () => string
CodeBlock javascript
// Usage
import { useDocument } from '@terrace-lang/js/document' import { useDocument } from '@terrace-lang/js/document'
const { head } = useDocument(...) const { head } = useDocument(...)
Heading 3 Document.tail() Heading 3 Document.tail()
class my-6 class my-6
Markdown
| Parameter | Type | Description
| -------------- | --------------------- | -----------------------------------------------------------------------
| **@returns** | string | The remainder of the line following the [head()](#document-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()](#document-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()](#document-tail) returns "An Important Document"
CodeBlock terrace
title An Important Document
CodeBlock typescript CodeBlock typescript
// Type Definition
tail: () => string tail: () => string
CodeBlock javascript
// Usage
import { useDocument } from '@terrace-lang/js/document' import { useDocument } from '@terrace-lang/js/document'
const { tail } = useDocument(...) const { tail } = useDocument(...)
Heading 3 Document.match() Heading 3 Document.match()
class my-6 class my-6
Markdown
| Parameter | Type | Description
| -------------- | --------------------- | -----------------------------------------------------------------------
| matchValue | string | A string to check against [head()](#document-head) for equality
| **@returns** | boolean | Whether the current [head()](#document-head) matches the passed value
Quickly check if the current line head matches a specified value
Shorthand for `matchValue === head()`
Given the following line
CodeBlock terrace
title An Important Document
Markdown
- `match('title')` returns `true`
- `match('somethingElse`) returns `false`
CodeBlock typescript CodeBlock typescript
// Type Definition
match: (matchValue: string) => boolean match: (matchValue: string) => boolean
CodeBlock javascript
// Usage
import { useDocument } from '@terrace-lang/js/document' import { useDocument } from '@terrace-lang/js/document'
const { match } = useDocument(...) const { match } = useDocument(...)
Heading 2 Reader API Heading 2 Reader API
class mt-12 class mt-12
Markdown
**TODO:** Document
Heading 3 Reader Heading 3 Reader
class my-6 class my-6
CodeBlock typescript CodeBlock typescript
// Type Definition
type Reader = () => string|null|Promise<string|null> type Reader = () => string|null|Promise<string|null>
Heading 3 createStringReader() Heading 3 createStringReader()
class my-6 class my-6
CodeBlock typescript CodeBlock typescript
function createFileReader(path: string): Reader // Type Definition
CodeBlock javascript function createStringReader(path: string): Reader
import { createStdinReader } from '@terrace-lang/js/readers/js-string'
// Import Path
import { createStringReader } from '@terrace-lang/js/readers/js-string'
Heading 3 createFileReader() Heading 3 createFileReader()
class my-6 class my-6
CodeBlock typescript CodeBlock typescript
// Type Definition
function createFileReader(path: string): Reader function createFileReader(path: string): Reader
CodeBlock javascript
import { createStdinReader } from '@terrace-lang/js/readers/node-readline' // Import Path
import { createFileReader } from '@terrace-lang/js/readers/node-readline'
Heading 3 createStdinReader() Heading 3 createStdinReader()
class my-6 class my-6
CodeBlock typescript CodeBlock typescript
// Type Definition
function createStdinReader(): Reader function createStdinReader(): Reader
CodeBlock javascript
// Import Path
import { createStdinReader } from '@terrace-lang/js/readers/node-readline' import { createStdinReader } from '@terrace-lang/js/readers/node-readline'
Heading 2 Contributing Heading 2 Contributing

View File

@ -1,8 +1,10 @@
import type { Reader } from './readers/reader' import type { Reader } from './readers/reader'
import { createLineData, parseLine } from './parser' import { createLineData, parseLine } from './parser'
// Container for a handful of convenience functions for parsing documents
// Obtained from useDocument() below
export type Document = { export type Document = {
next: (startLevel?: number) => Promise<boolean> next: (levelScope?: number) => Promise<boolean>
level: () => number, level: () => number,
line: (startOffset?: number) => string, line: (startOffset?: number) => string,
head: () => string, head: () => string,
@ -10,39 +12,147 @@ export type Document = {
match: (matchValue: string) => boolean match: (matchValue: string) => boolean
} }
/**
* Provides a simple set of convenience functions around parseLine for more ergonomic parsing of Terrace documents
*
* @param {Reader} reader When called, resolves to a string containing the next line in the document
* @param {String} indent 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
*/
export function useDocument (reader: Reader, indent: string = ' '): Document { export function useDocument (reader: Reader, indent: string = ' '): Document {
if (indent.length !== 1) throw new Error(`Terrace currently only allows single-character indent strings - you passed "${indent}"`)
const lineData = createLineData('', indent) const lineData = createLineData('', indent)
let repeat = false // If `repeatCurrentLine` is `true`, the following call to `next()` will repeat the current line in
async function next(startLevel: number = -1): Promise<boolean> { // the document and set `repeatCurrentLine` back to `false`
let repeatCurrentLine = false
/**
* 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.
*
* ```javascript
* while (await next()) {
* // Do something with each line.
* }
* ```
*
* @param {number} levelScope If specified, `next()` will return `false` when it encounters a line with a level at or below `levelScope`
* @returns {Promise<boolean>} Returns `true` after parsing a line, or `false` if the document has ended or a line at or below `levelScope` has been encountered.
*/
async function next(levelScope: number = -1): Promise<boolean> {
// Repeat the current line instead of parsing a new one if the previous call to next() // Repeat the current line instead of parsing a new one if the previous call to next()
// determined the current line to be out of its scope. // determined the current line to be out of its scope.
if (repeat) repeat = false if (repeatCurrentLine) repeatCurrentLine = false
// Otherwise parse the line normally. // Otherwise parse the line normally.
else { else {
// Load the next line from the line reader.
const line = await reader() const line = await reader()
// If there are no more lines, bail out. // If there are no more lines, bail out.
if (line == null) return false if (line == null) return false
// Populate lineData with parsed information from the current line.
lineData.line = line lineData.line = line
parseLine(lineData) parseLine(lineData)
} }
// If we shouldn't be handling this line, make the next call to next() repeat the current line. // If we shouldn't be handling this line, make the following call to next() repeat the current line.
// Allows a child loop to look forward, determine that the next line will be outside its purview, // 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. // and return control to the calling loop transparently without additional logic.
if (level() <= startLevel) { if (level() <= levelScope) {
repeat = true repeatCurrentLine = true
return false return false
} }
return true return true
} }
const level = () => lineData.level /**
const line = (startOffset: number = lineData.offsetHead) => lineData.line.slice(startOffset) * Returns the number of indent characters of the current line
const head = () => lineData.line.slice(lineData.offsetHead, lineData.offsetTail) *
const tail = () => lineData.line.slice(lineData.offsetTail + 1) // Skip the space * Given the following document, `level()` would return 0, 1, 2, and 5 respectively for each line
*
* ```terrace
* block
* block
* block
* block
* ```
* @returns {number} The indent level of the current line
*/
const level = (): number => lineData.level
/**
* 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
*
* ```terrace
* root
* sub-line
* ```
* `line()` on the second line returns "sub-line", trimming off the leading indent characters
* `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
*
* @param {number} startOffset 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`
*/
const line = (startOffset: number = lineData.level): string => lineData.line.slice(startOffset)
/**
* 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"
*
* ```terrace
* title An Important Document
* ```
* @returns {string} The `head` portion (first word) of a line
*/
const head = (): string => lineData.line.slice(lineData.offsetHead, lineData.offsetTail)
/**
* 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"
*
* ```terrace
* title An Important Document
* ```
* @returns {string} The remainder of the line following the `head()` portion, with no leading space
*/
const tail = (): string => lineData.line.slice(lineData.offsetTail + 1) // Skip the space
/**
* Quickly check if the current line head matches a specified value
*
* Shorthand for `matchValue === head()`
*
* Given the following line
*
* ```terrace
* title An Important Document
* ```
*
* `match('title')` returns `true`
* `match('somethingElse`) returns `false`
*
* @param {string} matchValue A string to check against `head()` for equality
* @returns {boolean}
*/
const match = (matchValue: string): boolean => matchValue === head() const match = (matchValue: string): boolean => matchValue === head()
return { return {

View File

@ -2,4 +2,3 @@ packages:
- "./docs" - "./docs"
- "./test" - "./test"
- "./packages/*" - "./packages/*"
- "./experiments/*"