Created
March 5, 2024 21:22
-
-
Save davidmurdoch/fe0eb0d3ee6e3e398f7c47bf07590d95 to your computer and use it in GitHub Desktop.
A TypeScript generator function that parses the given ini file (as a buffer) into sections, keys, and values (not tested, just for fun)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Enum representing the possible states of the parser. | |
*/ | |
enum State { | |
Start, | |
Key, | |
Value, | |
Comment, | |
} | |
// Define constants for ASCII values of special characters | |
const openBracket = 0x5b; // [ | |
const closeBracket = 0x5d; // ] | |
const equalSign = 0x3d; // = | |
const newline = 0x0a; // \n | |
const space = 0x20; // ' ' | |
const carriageReturn = 0x0d; // \r | |
const tab = 0x09; // \t | |
const backslash = 0x5c; // \ | |
const semicolon = 0x3b; // ; | |
const hash = 0x23; // # | |
/** | |
* A set of whitespace ASCII values for easy reference in trimming operations. | |
*/ | |
const whitespace = new Set([newline, space, tab, carriageReturn]); | |
/** | |
* Trims whitespace characters from both ends of a buffer. | |
* @param buf - The buffer to trim. | |
* @returns A new buffer trimmed of leading and trailing whitespace characters. | |
*/ | |
function trimBuffer(buf: Buffer): Buffer { | |
let start = 0, | |
end = buf.length - 1; | |
// Find the first non-whitespace character from the start | |
while (start <= end && whitespace.has(buf[start])) start++; | |
// Find the first non-whitespace character from the end | |
while (end >= start && whitespace.has(buf[end])) end--; | |
// Create a new buffer from the determined start and end | |
return buf.subarray(start, end + 1); | |
} | |
/** | |
* Generator function that parses the given buffer into sections, keys, and values. | |
* @param data - The buffer containing the data to parse. | |
* @yields A tuple containing the section name (if any), key, and value. | |
*/ | |
export function* parse(data: Buffer) { | |
// initialize parsing state | |
let state: State = State.Start; | |
let sectionName: Buffer | null = null; | |
let partStart = -1; | |
const current = { key: null as Buffer | null, value: [] as Buffer[] }; | |
/** | |
* Resets the current state and prepares the key-value data for yielding. | |
* @returns {[Buffer | null, Buffer, Buffer | null]} The section name, key, and value to yield. | |
*/ | |
function emit(): [Buffer | null, Buffer, Buffer | null] { | |
const key = current.key!; | |
let value: Buffer | null = null; | |
if (current.value.length === 1) { | |
value = trimBuffer(current.value[0]); | |
} else if (current.value.length > 1) { | |
value = trimBuffer(Buffer.concat(current.value)); | |
} | |
current.key = null; | |
current.value.length = 0; | |
partStart = -1; | |
return [sectionName, key, value]; | |
} | |
// Parse the buffer | |
for (let pos = 0; pos < data.length; pos++) { | |
const char = data[pos]; | |
switch (state) { | |
case State.Start: | |
if (char === openBracket) { | |
const endIndex = data.indexOf(closeBracket, pos); | |
if (endIndex !== -1) { | |
sectionName = data.subarray(pos + 1, endIndex); | |
pos = endIndex; | |
break; | |
} | |
} | |
if (char === semicolon || char === hash) { | |
state = State.Comment; | |
} else if (!whitespace.has(char)) { | |
partStart = pos; | |
state = State.Key; | |
} | |
break; | |
case State.Key: | |
if (char === equalSign) { | |
current.key = data.subarray(partStart, pos); | |
partStart = pos + 1; | |
state = State.Value; | |
} else if (char === newline || pos === data.length - 1) { | |
current.key = data.subarray( | |
partStart, | |
pos + (pos === data.length - 1 ? 1 : 0), | |
); | |
yield emit(); | |
state = State.Start; | |
} | |
break; | |
case State.Value: | |
if ( | |
char === backslash && | |
pos + 1 < data.length && | |
data[pos + 1] === newline | |
) { | |
current.value.push(data.subarray(partStart, pos)); | |
pos++; // Skip the backslash and newline | |
partStart = pos + 1; | |
} else if (char === newline) { | |
current.value.push(data.subarray(partStart, pos)); | |
yield emit(); | |
state = State.Start; | |
} | |
break; | |
case State.Comment: | |
if (char === newline) { | |
state = State.Start; | |
} | |
break; | |
} | |
if ( | |
state !== State.Comment && | |
char !== newline && | |
pos !== data.length - 1 | |
) { | |
if (partStart === -1) partStart = pos; | |
} else if (state === State.Value && pos === data.length - 1) { | |
// Edge case for values ending at the end of the data | |
current.value.push(data.subarray(partStart, data.length)); | |
yield emit(); | |
} | |
} | |
// Handle any final data not yet emitted | |
if (state === State.Value && partStart !== -1) { | |
current.value.push(data.subarray(partStart)); | |
yield emit(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
could be used like: