Add line number function to JS document parser.

This commit is contained in:
Joshua Bemenderfer 2024-09-03 16:14:16 -04:00
parent 8dca90037a
commit 70200a4091
3 changed files with 1570 additions and 30 deletions

1522
packages/js/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "@terrace-lang/js",
"description": "Terrace is a simple structured data syntax for configuration, content authoring, and DSLs.",
"version": "0.1.1",
"version": "0.1.2",
"license": "MIT",
"type": "module",
"repository": {

View File

@ -1,16 +1,17 @@
import type { Reader } from './readers/reader.js'
import { createLineData, parseLine } from './parser.js'
import type { Reader } from "./readers/reader.js";
import { createLineData, parseLine } from "./parser.js";
// Container for a handful of convenience functions for parsing documents
// Obtained from useDocument() below
export type Document = {
next: (levelScope?: number) => Promise<boolean>
level: () => number,
line: (startOffset?: number) => string,
head: () => string,
tail: () => string,
match: (matchValue: string) => boolean
}
next: (levelScope?: number) => Promise<boolean>;
level: () => number;
lineNumber: () => number;
line: (startOffset?: number) => string;
head: () => string;
tail: () => string;
match: (matchValue: string) => boolean;
};
/**
* Provides a simple set of convenience functions around parseLine for more ergonomic parsing of Terrace documents
@ -19,15 +20,19 @@ export type 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}"`)
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 currLine = ''
const lineData = createLineData(indent);
let currLine = "";
let currLineNumber = -1;
// 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
let repeatCurrentLine = false;
/**
* Advances the current position in the terrace document and populates lineData
* with the parsed information from that line
@ -54,28 +59,29 @@ export function useDocument (reader: Reader, indent: string = ' '): Document {
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 (repeatCurrentLine) repeatCurrentLine = false
if (repeatCurrentLine) repeatCurrentLine = false;
// Otherwise parse the line normally.
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 (line == null) return false
if (line == null) return false;
// Populate lineData with parsed information from the current line.
currLine = line
parseLine(currLine, lineData)
currLine = line;
currLineNumber++;
parseLine(currLine, lineData);
}
// 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() <= levelScope) {
repeatCurrentLine = true
return false
repeatCurrentLine = true;
return false;
}
return true
return true;
}
/**
@ -91,7 +97,14 @@ export function useDocument (reader: Reader, indent: string = ' '): Document {
* ```
* @returns {number} The indent level of the current line
*/
const level = (): number => lineData.level
const level = (): number => lineData.level;
/**
* Get the current line number, zero-indexed from first line read.
* @returns {number} The current line number, starting from zero.
*/
const lineNumber = (): number => currLineNumber;
/**
* Get a string with the current line contents. Skips all indent characters by default, but this can be configured with `startOffset`
*
@ -109,7 +122,9 @@ export function useDocument (reader: Reader, indent: string = ' '): Document {
* @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 => currLine.slice(startOffset)
const line = (startOffset: number = lineData.level): string =>
currLine.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.
@ -123,7 +138,9 @@ export function useDocument (reader: Reader, indent: string = ' '): Document {
* ```
* @returns {string} The `head` portion (first word) of a line
*/
const head = (): string => currLine.slice(lineData.offsetHead, lineData.offsetTail)
const head = (): string =>
currLine.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()`
*
@ -136,7 +153,7 @@ export function useDocument (reader: Reader, indent: string = ' '): Document {
* ```
* @returns {string} The remainder of the line following the `head()` portion, with no leading space
*/
const tail = (): string => currLine.slice(lineData.offsetTail + 1) // Skip the space
const tail = (): string => currLine.slice(lineData.offsetTail + 1); // Skip the space
/**
* Quickly check if the current line head matches a specified value
*
@ -154,14 +171,15 @@ export function useDocument (reader: Reader, indent: string = ' '): Document {
* @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 {
next,
level,
line,
lineNumber,
head,
tail,
match
}
match,
};
}