Skip to content

Instantly share code, notes, and snippets.

@Stmol
Created June 14, 2024 11:36
Show Gist options
  • Select an option

  • Save Stmol/e48175a1b12b3a2a7ce8354a0a928d05 to your computer and use it in GitHub Desktop.

Select an option

Save Stmol/e48175a1b12b3a2a7ce8354a0a928d05 to your computer and use it in GitHub Desktop.
test
type ValueType = number
const InvalidValue: ValueType = 1 << 0,
StringValue: ValueType = 1 << 1,
NumberValue: ValueType = 1 << 2,
NilValue: ValueType = 1 << 3,
BoolValue: ValueType = 1 << 4,
ArrayValue: ValueType = 1 << 5,
ObjectValue: ValueType = 1 << 6
let hexDigits: Uint8Array
let valueTypes: ValueType[]
function init() {
hexDigits = new Uint8Array(256)
hexDigits.fill(255)
// blah blah
// ....
}
type Iterator = {
buf: Uint8Array
head: number
tail: number
depth: number
captureStartedAt?: number
captured?: number
Error?: Error
Attachment?: any
}
export function NewIterator(cfg: any): Iterator {
return {
buf: new Uint8Array(),
head: 0,
tail: 0,
depth: 0,
}
}
function skipWhitespacesWithoutLoadMore(iter: Iterator): boolean {
for (let i = iter.head; i < iter.tail; i++) {
let c = String(iter.buf[i])
switch (c) {
case ' ':
case '\n':
case '\t':
case '\r':
continue
}
iter.head = i
return false
}
return true
}
export function ReportError(iter: Iterator, operation: string, msg: string): void {
if (iter.Error !== undefined) {
if (iter.Error instanceof Error) { // io.EOF type
return
}
}
let peekStart = iter.head - 10
if (peekStart < 0) {
peekStart = 0
}
// blah blah
iter.Error = new Error(
`${operation}: ${operation} error found in ${msg} byte of ...|${iter.head}|...`
)
}
@Stmol
Copy link
Author

Stmol commented Nov 21, 2025

Этот код представляет собой основу для написания низкоуровневого парсера (лексера). Скорее всего, это часть библиотеки для разбора формата данных (например, JSON) или собственного текстового протокола. Код оптимизирован для работы с байтами (бинарными данными), а не строками, что указывает на упор в производительность.

Давай разберем каждую часть детально.


1. Битовые маски для типов данных

type ValueType = number

const InvalidValue: ValueType = 1 << 0, // 1
    StringValue: ValueType = 1 << 1,    // 2
    NumberValue: ValueType = 1 << 2,    // 4
    NilValue: ValueType = 1 << 3,       // 8
    BoolValue: ValueType = 1 << 4,      // 16
    ArrayValue: ValueType = 1 << 5,     // 32
    ObjectValue: ValueType = 1 << 6     // 64

Что здесь происходит:
Здесь используется битовый сдвиг (<<) для создания уникальных флагов.

  • 1 << 0 — это $2^0 = 1$ (0000001 в двоичном виде)
  • 1 << 1 — это $2^1 = 2$ (0000010)
  • 1 << 2 — это $2^2 = 4$ (0000100)

Зачем это нужно:
Это классический паттерн Bitmask (Битовая маска). Использование чисел вместо строк (например, "String", "Number") работает намного быстрее и занимает меньше памяти. Также это позволяет комбинировать типы, если нужно (хотя здесь они выглядят взаимоисключающими).


2. Инициализация и глобальные таблицы

let hexDigits: Uint8Array
let valueTypes: ValueType[]

function init() {
    hexDigits = new Uint8Array(256)
    hexDigits.fill(255)
    // ...
}

Что здесь происходит:

  • Uint8Array — это типизированный массив, который хранит только байты (целые числа от 0 до 255). Это самый эффективный способ работы с бинарными данными в JS/TS.
  • hexDigits.fill(255) — заполняет массив значением 255 (часто используется как маркер "невалидного значения"). Обычно такие массивы используются как Lookup Table (таблица поиска), чтобы мгновенно проверять, является ли символ цифрой, буквой или разделителем, не используя медленные if/else или RegEx.

3. Структура Итератора (Iterator)

type Iterator = {
    buf: Uint8Array      // Буфер с данными (байты)
    head: number         // "Курсор": текущая позиция чтения
    tail: number         // Конец данных в буфере
    depth: number        // Глубина вложенности (для объектов/массивов)
    // ... поля для захвата строк и ошибок
}

Что здесь происходит:
Это сердце парсера. Представь ленту с данными (buf).

  1. head (Голова): Указывает, какой байт мы читаем прямо сейчас.
  2. tail (Хвост): Указывает, где заканчиваются данные, которые мы загрузили.

Функция NewIterator просто создает новый пустой итератор.


4. Логика пропуска пробелов

function skipWhitespacesWithoutLoadMore(iter: Iterator): boolean {
    for (let i = iter.head; i < iter.tail; i++) {
        let c = String(iter.buf[i]) // ⚠️ См. примечание ниже
        switch (c) {
            case ' ':
            case '\n': // перевод строки
            case '\t': // табуляция
            case '\r': // возврат каретки
              continue
        }
        iter.head = i // Нашли не пробел! Ставим курсор сюда.
        return false  // Возвращаем false (значит "не конец данных")
    }
    return true // Дошли до конца буфера (нужно загружать еще)
}

Что здесь происходит:
Функция бежит по буферу, пропуская "пустые" символы, которые не несут смысловой нагрузки (пробелы, переводы строк).

⚠️ Важное замечание по коду:
В строке let c = String(iter.buf[i]) есть странность.

  • iter.buf[i] возвращает число (код символа, например, 32 для пробела).
  • String(32) вернет строку "32".
  • А case ' ' проверяет именно символ пробела.
  • В реальном JS этот код не сработает как ожидается, если только buf каким-то чудом не хранит строки (что Uint8Array делать не умеет).
    Обычно в таких парсерах пишут так: switch (iter.buf[i]) { case 32: ... case 10: ... } (сравнивают числовые ASCII коды). Возможно, это псевдокод или специфическая реализация приведения типов.

5. Обработка ошибок (ReportError)

export function ReportError(iter: Iterator, operation: string, msg: string): void {
  // Если ошибка уже есть, не перезаписываем (особенно если это EOF - конец файла)
  if (iter.Error !== undefined) { ... }

  // Пытаемся захватить контекст (10 байт до ошибки), чтобы показать в логе
  let peekStart = iter.head - 10
  if (peekStart < 0) { peekStart = 0 }

  // Создаем объект ошибки с описанием
  iter.Error = new Error(
    `${operation}: ${operation} error found in ${msg} byte of ...|${iter.head}|...`
  )
}

Суть:
Эта функция нужна для "мягкого" падения. Она не выбрасывает throw (что остановило бы программу сразу), а записывает ошибку в состояние итератора. Это позволяет вызывающему коду самому решить, когда остановить обработку.

Итог

Этот код реализует курсорный подход к чтению данных.

  1. Он не загружает весь файл в одну большую строку (что экономит память).
  2. Он использует битовые флаги для быстрого определения типов.
  3. Он работает напрямую с байтами, что намного быстрее, чем работа со строками в JavaScript.

Это типичный паттерн для высокопроизводительных библиотек (например, для быстрого парсинга JSON, Protobuf или XML).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment