Skip to content

Instantly share code, notes, and snippets.

@adminy
Last active May 4, 2023 12:43
Show Gist options
  • Save adminy/f8fda2e036fb59a0d054a86d87455f83 to your computer and use it in GitHub Desktop.
Save adminy/f8fda2e036fb59a0d054a86d87455f83 to your computer and use it in GitHub Desktop.
JS Must have Builtins
Promise.parallel = (functions, limit) => new Promise(resolve => {
limit = limit ? Math.min(limit, functions.length) : functions.length
let resolved = 0
const taskDone = (index, value) => {
functions[index] = value // overwrite result into functions list
const nextIndex = resolved++ + limit
if (resolved === functions.length) {
return resolve(functions)
}
if (nextIndex >= functions.length) return
functions[nextIndex]()
.then(val => taskDone(nextIndex, val))
.catch(error => taskDone(nextIndex, error))
}
for (let i = 0; i < limit; i++) {
functions[i]()
.then(val => taskDone(i, val))
.catch(error => taskDone(i, error))
}
})
Promise.batch = async (fns, limit) => {
fns = [...fns]
limit = limit || fns.length
const outs = []
while (fns.length) {
const batch = fns.splice(0, limit)
const out = await Promise.all(batch.map(fn => fn()))
outs.push(...out)
}
return outs
}
Object.isObject = item => typeof item === 'object' && !Array.isArray(item)
Object.merge = (target, ...sources) => {
if (!sources.length) return target
const source = sources.shift()
if (Object.isObject(target) && Object.isObject(source))
for (const key in source)
if (Object.isObject(source[key])) {
!target[key] && Object.assign(target, { [key]: {} })
Object.merge(target[key], source[key])
} else
Object.assign(target, { [key]: source[key] })
return Object.merge(target, ...sources)
}
Array.prototype.isSubSet = function (B) {
const A = this
let i = 0; let j = 0
while (i < A.length && j < B.length) {
if (A[i] === B[j]) {
i++
j++
if (j === B.length) return true
} else {
i = i - j + 1
j = 0
}
}
return false
}
Array.prototype.same = function(b) { return this.length === b.length && this.every((value, index) => value === b[index]) }
const fs = require('fs')
// Named constants with unique integer values
const C = {}
// Tokens
const LEFT_BRACE = C.LEFT_BRACE = 0x1
const RIGHT_BRACE = C.RIGHT_BRACE = 0x2
const LEFT_BRACKET = C.LEFT_BRACKET = 0x3
const RIGHT_BRACKET = C.RIGHT_BRACKET = 0x4
const COLON = C.COLON = 0x5
const COMMA = C.COMMA = 0x6
const TRUE = C.TRUE = 0x7
const FALSE = C.FALSE = 0x8
const NULL = C.NULL = 0x9
const STRING = C.STRING = 0xa
const NUMBER = C.NUMBER = 0xb
// Tokenizer States
const START = C.START = 0x11
const STOP = C.STOP = 0x12
const TRUE1 = C.TRUE1 = 0x21
const TRUE2 = C.TRUE2 = 0x22
const TRUE3 = C.TRUE3 = 0x23
const FALSE1 = C.FALSE1 = 0x31
const FALSE2 = C.FALSE2 = 0x32
const FALSE3 = C.FALSE3 = 0x33
const FALSE4 = C.FALSE4 = 0x34
const NULL1 = C.NULL1 = 0x41
const NULL2 = C.NULL2 = 0x42
const NULL3 = C.NULL3 = 0x43
const NUMBER1 = C.NUMBER1 = 0x51
const NUMBER3 = C.NUMBER3 = 0x53
const STRING1 = C.STRING1 = 0x61
const STRING2 = C.STRING2 = 0x62
const STRING3 = C.STRING3 = 0x63
const STRING4 = C.STRING4 = 0x64
const STRING5 = C.STRING5 = 0x65
const STRING6 = C.STRING6 = 0x66
// Parser States
const VALUE = C.VALUE = 0x71
const KEY = C.KEY = 0x72
// Parser Modes
const OBJECT = C.OBJECT = 0x81
const ARRAY = C.ARRAY = 0x82
// Character constants
const BACK_SLASH = '\\'.charCodeAt(0)
const FORWARD_SLASH = '/'.charCodeAt(0)
const BACKSPACE = '\b'.charCodeAt(0)
const FORM_FEED = '\f'.charCodeAt(0)
const NEWLINE = '\n'.charCodeAt(0)
const CARRIAGE_RETURN = '\r'.charCodeAt(0)
const TAB = '\t'.charCodeAt(0)
const STRING_BUFFER_SIZE = 64 * 1024
function Parser () {
this.tState = START
this.value = undefined
this.string = undefined // string data
this.stringBuffer = Buffer.alloc(STRING_BUFFER_SIZE)
this.stringBufferOffset = 0
this.unicode = undefined // unicode escapes
this.highSurrogate = undefined
this.key = undefined
this.mode = undefined
this.stack = []
this.state = VALUE
this.bytes_remaining = 0 // number of bytes remaining in multi byte utf8 char to read after split boundary
this.bytes_in_sequence = 0 // bytes in multi byte utf8 char to read
this.temp_buffs = { 2: Buffer.alloc(2), 3: Buffer.alloc(3), 4: Buffer.alloc(4) } // for rebuilding chars split before boundary is reached
// Stream offset
this.offset = -1
}
// Slow code to string converter (only used when throwing syntax errors)
Parser.toknam = function (code) {
const keys = Object.keys(C)
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
if (C[key] === code) { return key }
}
return code && ('0x' + code.toString(16))
}
const proto = Parser.prototype
proto.onError = function (err) { throw err }
proto.charError = function (buffer, i) {
this.tState = STOP
this.onError(new Error('Unexpected ' + JSON.stringify(String.fromCharCode(buffer[i])) + ' at position ' + i + ' in state ' + Parser.toknam(this.tState)))
}
proto.appendStringChar = function (char) {
if (this.stringBufferOffset >= STRING_BUFFER_SIZE) {
this.string += this.stringBuffer.toString('utf8')
this.stringBufferOffset = 0
}
this.stringBuffer[this.stringBufferOffset++] = char
}
proto.appendStringBuf = function (buf, start, end) {
let size = buf.length
if (typeof start === 'number') {
if (typeof end === 'number') {
if (end < 0) {
// adding a negative end decreeses the size
size = buf.length - start + end
} else {
size = end - start
}
} else {
size = buf.length - start
}
}
if (size < 0) {
size = 0
}
if (this.stringBufferOffset + size > STRING_BUFFER_SIZE) {
this.string += this.stringBuffer.toString('utf8', 0, this.stringBufferOffset)
this.stringBufferOffset = 0
}
buf.copy(this.stringBuffer, this.stringBufferOffset, start, end)
this.stringBufferOffset += size
}
proto.write = function (buffer) {
if (typeof buffer === 'string') buffer = Buffer.from(buffer)
let n
for (let i = 0, l = buffer.length; i < l; i++) {
if (this.tState === START) {
n = buffer[i]
this.offset++
if (n === 0x7b) {
this.onToken(LEFT_BRACE, '{') // {
} else if (n === 0x7d) {
this.onToken(RIGHT_BRACE, '}') // }
} else if (n === 0x5b) {
this.onToken(LEFT_BRACKET, '[') // [
} else if (n === 0x5d) {
this.onToken(RIGHT_BRACKET, ']') // ]
} else if (n === 0x3a) {
this.onToken(COLON, ':') // :
} else if (n === 0x2c) {
this.onToken(COMMA, ',') // ,
} else if (n === 0x74) {
this.tState = TRUE1 // t
} else if (n === 0x66) {
this.tState = FALSE1 // f
} else if (n === 0x6e) {
this.tState = NULL1 // n
} else if (n === 0x22) { // "
this.string = ''
this.stringBufferOffset = 0
this.tState = STRING1
} else if (n === 0x2d) {
this.string = '-'; this.tState = NUMBER1 // -
} else {
if (n >= 0x30 && n < 0x40) { // 1-9
this.string = String.fromCharCode(n); this.tState = NUMBER3
} else if (n === 0x20 || n === 0x09 || n === 0x0a || n === 0x0d) {
// whitespace
} else {
return this.charError(buffer, i)
}
}
} else if (this.tState === STRING1) { // After open quote
n = buffer[i] // get current byte from buffer
// check for carry over of a multi byte char split between data chunks
// & fill temp buffer it with start of this data chunk up to the boundary limit set in the last iteration
if (this.bytes_remaining > 0) {
let j
for (j = 0; j < this.bytes_remaining; j++) {
this.temp_buffs[this.bytes_in_sequence][this.bytes_in_sequence - this.bytes_remaining + j] = buffer[j]
}
this.appendStringBuf(this.temp_buffs[this.bytes_in_sequence])
this.bytes_in_sequence = this.bytes_remaining = 0
i = i + j - 1
} else if (this.bytes_remaining === 0 && n >= 128) { // else if no remainder bytes carried over, parse multi byte (>=128) chars one at a time
if (n <= 193 || n > 244) {
return this.onError(new Error('Invalid UTF-8 character at position ' + i + ' in state ' + Parser.toknam(this.tState)))
}
if ((n >= 194) && (n <= 223)) this.bytes_in_sequence = 2
if ((n >= 224) && (n <= 239)) this.bytes_in_sequence = 3
if ((n >= 240) && (n <= 244)) this.bytes_in_sequence = 4
if ((this.bytes_in_sequence + i) > buffer.length) { // if bytes needed to complete char fall outside buffer length, we have a boundary split
for (let k = 0; k <= (buffer.length - 1 - i); k++) {
this.temp_buffs[this.bytes_in_sequence][k] = buffer[i + k] // fill temp buffer of correct size with bytes available in this chunk
}
this.bytes_remaining = (i + this.bytes_in_sequence) - buffer.length
i = buffer.length - 1
} else {
this.appendStringBuf(buffer, i, i + this.bytes_in_sequence)
i = i + this.bytes_in_sequence - 1
}
} else if (n === 0x22) {
this.tState = START
this.string += this.stringBuffer.toString('utf8', 0, this.stringBufferOffset)
this.stringBufferOffset = 0
this.onToken(STRING, this.string)
this.offset += Buffer.byteLength(this.string, 'utf8') + 1
this.string = undefined
} else if (n === 0x5c) {
this.tState = STRING2
} else if (n >= 0x20) { this.appendStringChar(n) } else {
return this.charError(buffer, i)
}
} else if (this.tState === STRING2) { // After backslash
n = buffer[i]
if (n === 0x22) {
this.appendStringChar(n); this.tState = STRING1
} else if (n === 0x5c) {
this.appendStringChar(BACK_SLASH); this.tState = STRING1
} else if (n === 0x2f) {
this.appendStringChar(FORWARD_SLASH); this.tState = STRING1
} else if (n === 0x62) {
this.appendStringChar(BACKSPACE); this.tState = STRING1
} else if (n === 0x66) {
this.appendStringChar(FORM_FEED); this.tState = STRING1
} else if (n === 0x6e) {
this.appendStringChar(NEWLINE); this.tState = STRING1
} else if (n === 0x72) {
this.appendStringChar(CARRIAGE_RETURN); this.tState = STRING1
} else if (n === 0x74) {
this.appendStringChar(TAB); this.tState = STRING1
} else if (n === 0x75) {
this.unicode = ''; this.tState = STRING3
} else {
return this.charError(buffer, i)
}
} else if (this.tState === STRING3 || this.tState === STRING4 || this.tState === STRING5 || this.tState === STRING6) { // unicode hex codes
n = buffer[i]
// 0-9 A-F a-f
if ((n >= 0x30 && n < 0x40) || (n > 0x40 && n <= 0x46) || (n > 0x60 && n <= 0x66)) {
this.unicode += String.fromCharCode(n)
if (this.tState++ === STRING6) {
const intVal = parseInt(this.unicode, 16)
this.unicode = undefined
if (this.highSurrogate !== undefined && intVal >= 0xDC00 && intVal < (0xDFFF + 1)) { // <56320,57343> - lowSurrogate
this.appendStringBuf(Buffer.from(String.fromCharCode(this.highSurrogate, intVal)))
this.highSurrogate = undefined
} else if (this.highSurrogate === undefined && intVal >= 0xD800 && intVal < (0xDBFF + 1)) { // <55296,56319> - highSurrogate
this.highSurrogate = intVal
} else {
if (this.highSurrogate !== undefined) {
this.appendStringBuf(Buffer.from(String.fromCharCode(this.highSurrogate)))
this.highSurrogate = undefined
}
this.appendStringBuf(Buffer.from(String.fromCharCode(intVal)))
}
this.tState = STRING1
}
} else {
return this.charError(buffer, i)
}
} else if (this.tState === NUMBER1 || this.tState === NUMBER3) {
n = buffer[i]
switch (n) {
case 0x30: // 0
case 0x31: // 1
case 0x32: // 2
case 0x33: // 3
case 0x34: // 4
case 0x35: // 5
case 0x36: // 6
case 0x37: // 7
case 0x38: // 8
case 0x39: // 9
case 0x2e: // .
case 0x65: // e
case 0x45: // E
case 0x2b: // +
case 0x2d: // -
this.string += String.fromCharCode(n)
this.tState = NUMBER3
break
default:
this.tState = START
if (this.numberReviver(this.string, buffer, i)) {
return this.numberReviver(this.string, buffer, i)
}
this.offset += this.string.length - 1
this.string = undefined
i--
break
}
} else if (this.tState === TRUE1) { // r
if (buffer[i] === 0x72) { this.tState = TRUE2 } else { return this.charError(buffer, i) }
} else if (this.tState === TRUE2) { // u
if (buffer[i] === 0x75) { this.tState = TRUE3 } else { return this.charError(buffer, i) }
} else if (this.tState === TRUE3) { // e
if (buffer[i] === 0x65) { this.tState = START; this.onToken(TRUE, true); this.offset += 3 } else { return this.charError(buffer, i) }
} else if (this.tState === FALSE1) { // a
if (buffer[i] === 0x61) { this.tState = FALSE2 } else { return this.charError(buffer, i) }
} else if (this.tState === FALSE2) { // l
if (buffer[i] === 0x6c) { this.tState = FALSE3 } else { return this.charError(buffer, i) }
} else if (this.tState === FALSE3) { // s
if (buffer[i] === 0x73) { this.tState = FALSE4 } else { return this.charError(buffer, i) }
} else if (this.tState === FALSE4) { // e
if (buffer[i] === 0x65) { this.tState = START; this.onToken(FALSE, false); this.offset += 4 } else { return this.charError(buffer, i) }
} else if (this.tState === NULL1) { // u
if (buffer[i] === 0x75) { this.tState = NULL2 } else { return this.charError(buffer, i) }
} else if (this.tState === NULL2) { // l
if (buffer[i] === 0x6c) { this.tState = NULL3 } else { return this.charError(buffer, i) }
} else if (this.tState === NULL3) { // l
if (buffer[i] === 0x6c) { this.tState = START; this.onToken(NULL, null); this.offset += 3 } else { return this.charError(buffer, i) }
}
}
}
proto.parseError = function (token, value) {
this.tState = STOP
this.onError(new Error('Unexpected ' + Parser.toknam(token) + (value ? ('(' + JSON.stringify(value) + ')') : '') + ' in state ' + Parser.toknam(this.state)))
}
proto.push = function () {
this.stack.push({ value: this.value, key: this.key, mode: this.mode })
}
proto.pop = function () {
const value = this.value
const parent = this.stack.pop()
this.value = parent.value
this.key = parent.key
this.mode = parent.mode
this.emit(value)
if (!this.mode) { this.state = VALUE }
}
proto.emit = function (value) {
if (this.mode) { this.state = COMMA }
this._lastValue = value
}
proto.onToken = function (token, value) {
if (this.state === VALUE) {
if (token === STRING || token === NUMBER || token === TRUE || token === FALSE || token === NULL) {
if (this.value) {
this.value[this.key] = value
}
this.emit(value)
} else if (token === LEFT_BRACE) {
this.push()
if (this.value) {
this.value = this.value[this.key] = {}
} else {
this.value = {}
}
this.key = undefined
this.state = KEY
this.mode = OBJECT
} else if (token === LEFT_BRACKET) {
this.push()
if (this.value) {
this.value = this.value[this.key] = []
} else {
this.value = []
}
this.key = 0
this.mode = ARRAY
this.state = VALUE
} else if (token === RIGHT_BRACE) {
if (this.mode === OBJECT) {
this.pop()
} else {
return this.parseError(token, value)
}
} else if (token === RIGHT_BRACKET) {
if (this.mode === ARRAY) {
this.pop()
} else {
return this.parseError(token, value)
}
} else {
return this.parseError(token, value)
}
} else if (this.state === KEY) {
if (token === STRING) {
this.key = value
this.state = COLON
} else if (token === RIGHT_BRACE) {
this.pop()
} else {
return this.parseError(token, value)
}
} else if (this.state === COLON) {
if (token === COLON) { this.state = VALUE } else { return this.parseError(token, value) }
} else if (this.state === COMMA) {
if (token === COMMA) {
if (this.mode === ARRAY) { this.key++; this.state = VALUE } else if (this.mode === OBJECT) { this.state = KEY }
} else if ((token === RIGHT_BRACKET && this.mode === ARRAY) || (token === RIGHT_BRACE && this.mode === OBJECT)) {
this.pop()
} else {
return this.parseError(token, value)
}
} else {
return this.parseError(token, value)
}
}
// Override to implement your own number reviver.
// Any value returned is treated as error and will interrupt parsing.
proto.numberReviver = function (text, buffer, i) {
const result = Number(text)
if (isNaN(result)) {
return this.charError(buffer, i)
}
if ((text.match(/[0-9]+/) === text) && (result.toString() !== text)) {
// Long string of digits which is an ID string and not valid and/or safe JavaScript integer Number
this.onToken(STRING, text)
} else {
this.onToken(NUMBER, result)
}
}
JSON.streamableParse = async path => {
const readable = fs.createReadStream(path, { encoding: 'utf8' })
const parser = new Parser()
for await (const chunk of readable) {
parser.write(chunk)
}
return parser._lastValue
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment