Document most of the JS 'Document' API
This commit is contained in:
		| @@ -19,7 +19,7 @@ Section light | ||||
|     Markdown | ||||
|       Documentation is available for the following languages: | ||||
|       - [C](/docs/c/) - 10% Complete | ||||
|       - [JavaScript](/docs/javascript/) - 20% Complete | ||||
|       - [JavaScript](/docs/javascript/) - 50% Complete | ||||
|       - [Python](/docs/python/) - 0% Complete | ||||
|  | ||||
|     Heading 2 Getting Started | ||||
|   | ||||
| @@ -19,7 +19,7 @@ Section light | ||||
|     Markdown | ||||
|       Documentation is available for the following languages: | ||||
|       - [C](/docs/c/) - 10% Complete | ||||
|       - [JavaScript](/docs/javascript/) - 20% Complete | ||||
|       - [JavaScript](/docs/javascript/) - 50% Complete | ||||
|       - [Python](/docs/python/) - 0% Complete | ||||
|  | ||||
|     Heading 2 Getting Started | ||||
| @@ -129,10 +129,13 @@ Section light | ||||
|  | ||||
|     Heading 2 Core API | ||||
|       class mt-12 | ||||
|     Markdown | ||||
|       **TODO:** Document | ||||
|  | ||||
|     Heading 3 LineData | ||||
|       class my-6 | ||||
|     CodeBlock typescript | ||||
|       // Type Definition | ||||
|       type LineData = { | ||||
|         line: string; | ||||
|         indent: string; | ||||
| @@ -160,16 +163,30 @@ Section light | ||||
|  | ||||
|     Heading 3 useDocument() | ||||
|       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 | ||||
|       // Type Definition | ||||
|       function useDocument (reader: Reader, indent: string = ' '): Document | ||||
|     CodeBlock javascript | ||||
|  | ||||
|       // Import Path | ||||
|       import { useDocument } from '@terrace-lang/js/document' | ||||
|  | ||||
|     Heading 3 Document | ||||
|       class my-6 | ||||
|     Markdown | ||||
|       Container for a handful of convenience functions for parsing documents. | ||||
|       Obtained from [useDocument()](#usedocument) above | ||||
|     CodeBlock typescript | ||||
|       // Type Definition | ||||
|       type Document = { | ||||
|         next: (startLevel?: number) => Promise<boolean> | ||||
|         next: (levelScope?: number) => Promise<boolean> | ||||
|         level: () => number, | ||||
|         line: (startOffset?: number) => string, | ||||
|         head: () => string, | ||||
| @@ -179,79 +196,198 @@ Section light | ||||
|  | ||||
|     Heading 3 Document.next() | ||||
|       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 | ||||
|       next: (startLevel?: number) => Promise<boolean> | ||||
|     CodeBlock javascript | ||||
|       // 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. | ||||
|       } | ||||
|  | ||||
|     Heading 3 Document.level() | ||||
|       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 | ||||
|       // Type Definition | ||||
|       level: () => number | ||||
|     CodeBlock javascript | ||||
|  | ||||
|       // Usage | ||||
|       import { useDocument } from '@terrace-lang/js/document' | ||||
|       const { level } = useDocument(...) | ||||
|  | ||||
|     Heading 3 Document.line() | ||||
|       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 "    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 | ||||
|       // Type Definition | ||||
|       line: (startOffset?: number) => string | ||||
|     CodeBlock javascript | ||||
|  | ||||
|       // Usage | ||||
|       import { useDocument } from '@terrace-lang/js/document' | ||||
|       const { line } = useDocument(...) | ||||
|  | ||||
|     Heading 3 Document.head() | ||||
|       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 | ||||
|       // Type Definition | ||||
|       head: () => string | ||||
|     CodeBlock javascript | ||||
|  | ||||
|       // Usage | ||||
|       import { useDocument } from '@terrace-lang/js/document' | ||||
|       const { head } = useDocument(...) | ||||
|  | ||||
|     Heading 3 Document.tail() | ||||
|       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 | ||||
|       // Type Definition | ||||
|       tail: () => string | ||||
|     CodeBlock javascript | ||||
|  | ||||
|       // Usage | ||||
|       import { useDocument } from '@terrace-lang/js/document' | ||||
|       const { tail } = useDocument(...) | ||||
|  | ||||
|     Heading 3 Document.match() | ||||
|       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 | ||||
|       // Type Definition | ||||
|       match: (matchValue: string) => boolean | ||||
|     CodeBlock javascript | ||||
|  | ||||
|       // Usage | ||||
|       import { useDocument } from '@terrace-lang/js/document' | ||||
|       const { match } = useDocument(...) | ||||
|  | ||||
|     Heading 2 Reader API | ||||
|       class mt-12 | ||||
|     Markdown | ||||
|       **TODO:** Document | ||||
|  | ||||
|     Heading 3 Reader | ||||
|       class my-6 | ||||
|     CodeBlock typescript | ||||
|       // Type Definition | ||||
|       type Reader = () => string|null|Promise<string|null> | ||||
|  | ||||
|     Heading 3 createStringReader() | ||||
|       class my-6 | ||||
|     CodeBlock typescript | ||||
|       function createFileReader(path: string): Reader | ||||
|     CodeBlock javascript | ||||
|       import { createStdinReader } from '@terrace-lang/js/readers/js-string' | ||||
|       // Type Definition | ||||
|       function createStringReader(path: string): Reader | ||||
|  | ||||
|       // Import Path | ||||
|       import { createStringReader } from '@terrace-lang/js/readers/js-string' | ||||
|  | ||||
|     Heading 3 createFileReader() | ||||
|       class my-6 | ||||
|     CodeBlock typescript | ||||
|       // Type Definition | ||||
|       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() | ||||
|       class my-6 | ||||
|     CodeBlock typescript | ||||
|       // Type Definition | ||||
|       function createStdinReader(): Reader | ||||
|     CodeBlock javascript | ||||
|  | ||||
|       // Import Path | ||||
|       import { createStdinReader } from '@terrace-lang/js/readers/node-readline' | ||||
|  | ||||
|     Heading 2 Contributing | ||||
|   | ||||
| @@ -1,8 +1,10 @@ | ||||
| import type { Reader } from './readers/reader' | ||||
| import { createLineData, parseLine } from './parser' | ||||
|  | ||||
| // Container for a handful of convenience functions for parsing documents | ||||
| // Obtained from useDocument() below | ||||
| export type Document = { | ||||
|   next: (startLevel?: number) => Promise<boolean> | ||||
|   next: (levelScope?: number) => Promise<boolean> | ||||
|   level: () => number, | ||||
|   line: (startOffset?: number) => string, | ||||
|   head: () => string, | ||||
| @@ -10,39 +12,147 @@ export type Document = { | ||||
|   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 { | ||||
|   if (indent.length !== 1) throw new Error(`Terrace currently only allows single-character indent strings - you passed "${indent}"`) | ||||
|  | ||||
|   const lineData = createLineData('', indent) | ||||
|  | ||||
|   let repeat = false | ||||
|   async function next(startLevel: number = -1): Promise<boolean> { | ||||
|   // If `repeatCurrentLine` is `true`, the following call to `next()` will repeat the current line in | ||||
|   // 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() | ||||
|     // determined the current line to be out of its scope. | ||||
|     if (repeat) repeat = false | ||||
|     if (repeatCurrentLine) repeatCurrentLine = false | ||||
|     // Otherwise parse the line normally. | ||||
|     else { | ||||
|       // Load the next line from the line reader. | ||||
|       const line = await reader() | ||||
|       // If there are no more lines, bail out. | ||||
|       if (line == null) return false | ||||
|  | ||||
|       // Populate lineData with parsed information from the current line. | ||||
|       lineData.line = line | ||||
|       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, | ||||
|     // and return control to the calling loop transparently without additional logic. | ||||
|     if (level() <= startLevel) { | ||||
|       repeat = true | ||||
|     if (level() <= levelScope) { | ||||
|       repeatCurrentLine = true | ||||
|       return false | ||||
|     } | ||||
|  | ||||
|     return true | ||||
|   } | ||||
|  | ||||
|   const level = () => lineData.level | ||||
|   const line = (startOffset: number = lineData.offsetHead) => lineData.line.slice(startOffset) | ||||
|   const head = () => lineData.line.slice(lineData.offsetHead, lineData.offsetTail) | ||||
|   const tail = () => lineData.line.slice(lineData.offsetTail + 1) // Skip the space | ||||
|   /** | ||||
|    * 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 | ||||
|    * | ||||
|    * ```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() | ||||
|  | ||||
|   return { | ||||
|   | ||||
| @@ -2,4 +2,3 @@ packages: | ||||
|   - "./docs" | ||||
|   - "./test" | ||||
|   - "./packages/*" | ||||
|   - "./experiments/*" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Joshua Bemenderfer
					Joshua Bemenderfer