Skip to content

Instantly share code, notes, and snippets.

@raynirola
Created June 23, 2024 17:53
Show Gist options
  • Save raynirola/43a74cf4f486407d94796928f7ce5bdf to your computer and use it in GitHub Desktop.
Save raynirola/43a74cf4f486407d94796928f7ce5bdf to your computer and use it in GitHub Desktop.
class Tokenizer {
private input: string = '';
private position: number = 0;
public appendData(data: string): void {
this.input += data;
}
public getNextToken(): string | null {
this.skipWhitespace();
if (this.position >= this.input.length) return null;
const char = this.input[this.position];
if (char === '{' || char === '}' || char === '[' || char === ']' || char === ':' || char === ',') {
this.position++;
return char;
}
if (char === '"') {
return this.readString();
}
if (char >= '0' && char <= '9' || char === '-') {
return this.readNumber();
}
return this.readLiteral();
}
private skipWhitespace(): void {
while (this.position < this.input.length && /\s/.test(this.input[this.position])) {
this.position++;
}
}
private readString(): string {
let result = '';
this.position++; // Skip opening quote
while (this.position < this.input.length && this.input[this.position] !== '"') {
result += this.input[this.position++];
}
this.position++; // Skip closing quote
return result;
}
private readNumber(): string {
let result = '';
while (this.position < this.input.length && /[0-9.-]/.test(this.input[this.position])) {
result += this.input[this.position++];
}
return result;
}
private readLiteral(): string {
let result = '';
while (this.position < this.input.length && /[a-zA-Z]/.test(this.input[this.position])) {
result += this.input[this.position++];
}
return result;
}
}
import { Readable } from 'stream';
class Parser<T> {
private tokenizer: Tokenizer;
private currentToken: string | null;
constructor() {
this.tokenizer = new Tokenizer();
this.currentToken = null;
}
// Overload method signatures
public parse(input: string): T;
public parse(input: Buffer): T;
public parse(input: Readable): Promise<T>;
// Implementation of the parse method
public parse(input: string | Buffer | Readable): T | Promise<T> {
if (typeof input === 'string' || input instanceof Buffer) {
this.tokenizer.appendData(input.toString());
this.currentToken = this.tokenizer.getNextToken();
return this.parseValue() as T;
} else if (input instanceof Readable) {
return this.parseStream(input);
} else {
throw new Error('Unsupported input type');
}
}
private async parseStream(stream: Readable): Promise<T> {
return new Promise((resolve, reject) => {
let result: T | null = null;
stream.on('data', chunk => {
try {
this.tokenizer.appendData(chunk.toString());
result = this.parseValue() as T;
} catch (err) {
reject(err);
}
});
stream.on('end', () => {
try {
if (result === null) {
result = this.parseValue() as T; // Final attempt to parse remaining data
}
resolve(result as T);
} catch (err) {
reject(err);
}
});
stream.on('error', reject);
});
}
private parseValue(): JSONValue {
if (this.currentToken === null) throw new Error("Unexpected end of input");
switch (this.currentToken) {
case '{':
return this.parseObject();
case '[':
return this.parseArray();
case '"':
return this.tokenizer.getNextToken()!;
case 'true':
this.advance();
return true;
case 'false':
this.advance();
return false;
case 'null':
this.advance();
return null;
default:
return this.parseNumber();
}
}
private parseObject(): JSONObject {
const obj: JSONObject = {};
this.advance(); // Skip '{'
while (this.currentToken !== '}') {
const key = this.parseValue() as string;
this.advance(); // Skip ':'
const value = this.parseValue();
obj[key] = value;
if (this.currentToken === ',') this.advance();
}
this.advance(); // Skip '}'
return obj;
}
private parseArray(): JSONArray {
const arr: JSONArray = [];
this.advance(); // Skip '['
while (this.currentToken !== ']') {
arr.push(this.parseValue());
if (this.currentToken === ',') this.advance();
}
this.advance(); // Skip ']'
return arr;
}
private parseNumber(): number {
const num = Number(this.currentToken);
this.advance();
return num;
}
private advance(): void {
this.currentToken = this.tokenizer.getNextToken();
}
}
import { createReadStream } from 'fs';
// Using a string
const jsonString = '{"name": "John", "age": 30, "isStudent": false}';
const parserString = new Parser<{ name: string; age: number; isStudent: boolean }>();
const resultString = parserString.parse(jsonString);
console.log(resultString);
// Using a buffer
const jsonBuffer = Buffer.from(jsonString);
const parserBuffer = new Parser<{ name: string; age: number; isStudent: boolean }>();
const resultBuffer = parserBuffer.parse(jsonBuffer);
console.log(resultBuffer);
// Using a file stream
const stream = createReadStream('path/to/file.json');
const parserStream = new Parser<{ name: string; age: number; isStudent: boolean }>();
parserStream.parse(stream)
.then(result => console.log(result))
.catch(error => console.error(error));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment