Use sync parser

pull/251/head
Anton Medvedev 1 year ago
parent 8d4292a41b
commit ef426202ab
No known key found for this signature in database

@ -1,4 +1,6 @@
#!/usr/bin/env node
'use strict'
void async function main() {
const process = await import('node:process')
const args = process.argv.slice(2)
@ -8,9 +10,8 @@ void async function main() {
return
}
JSON.stringify = value => stringify(value)
for await (const json of parseJson()) {
const stdin = await createStdinGenerator()
for (const json of parseJson(stdin)) {
// let json
// if (['-r', '--raw'].includes(args[0])) {
// args.shift()
@ -26,7 +27,7 @@ void async function main() {
output = await transform(output, code)
} catch (err) {
printErr(err)
return 1
process.exit(1)
}
if (typeof output === 'undefined')
@ -34,7 +35,7 @@ void async function main() {
else if (typeof output === 'string')
console.log(output)
else
console.log(stringify(output, true))
console.log(stringify(output, process.stdout.isTTY))
function printErr(err) {
let pre = args.slice(0, i).join(' ')
@ -48,7 +49,7 @@ void async function main() {
)
}
}
}().then(exitCode => process.exitCode = exitCode)
}()
async function transform(json, code) {
if ('.' === code)
@ -123,66 +124,89 @@ function groupBy(keyOrFunction) {
}
}
async function* stdin() {
const process = await import('node:process')
process.stdin.setEncoding('utf8')
for await (const chunk of process.stdin)
for (const ch of chunk)
async function createStdinGenerator() {
const fs = await import('node:fs')
const {Buffer} = await import('node:buffer')
const {StringDecoder} = await import('node:string_decoder')
const decoder = new StringDecoder('utf8')
return function* () {
while (true) {
const buffer = Buffer.alloc(4_096)
let bytesRead
try {
bytesRead = fs.readSync(0, buffer, 0, buffer.length, null)
} catch (e) {
if (e.code === 'EAGAIN' || e.code === 'EWOULDBLOCK') {
sleepSync(10)
continue
}
if (e.code === 'EOF') break
throw e
}
if (bytesRead === 0) break
for (const ch of decoder.write(buffer.subarray(0, bytesRead)))
yield ch
}
for (const ch of decoder.end())
yield ch
}()
}
function sleepSync(ms) {
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)
}
async function* parseJson() {
const gen = stdin()
let pos = 0, buffer = '', lastChar, done
function* parseJson(stdin) {
let pos = 0, buffer = '', lastChar, done = false
async function next() {
({value: lastChar, done: done} = await gen.next())
function next() {
({value: lastChar, done} = stdin.next())
pos++
buffer = buffer.slice(-10) + lastChar
}
function printErrorSnippet() {
const snippet = buffer + lastChar
function errorSnippet() {
let nextChars = ''
for (let i = 0; i < 10; i++) {
const {value: ch} = gen.next()
const {value: ch} = stdin.next()
if (ch === '\n') break
nextChars += ch || ''
}
return `Error snippet: ${snippet}${nextChars}`
return `\n\n ${buffer}${nextChars}\n ${'.'.repeat(buffer.length - 1)}^\n`
}
await next()
next()
while (!done) {
const value = await parseValue()
const value = parseValue()
expectValue(value)
yield value
}
async function parseValue() {
await skipWhitespace()
function parseValue() {
skipWhitespace()
const value =
await parseString() ??
await parseNumber() ??
await parseObject() ??
await parseArray() ??
await parseKeyword('true', true) ??
await parseKeyword('false', false) ??
await parseKeyword('null', null)
await skipWhitespace()
parseString() ??
parseNumber() ??
parseObject() ??
parseArray() ??
parseKeyword('true', true) ??
parseKeyword('false', false) ??
parseKeyword('null', null)
skipWhitespace()
return value
}
async function parseString() {
function parseString() {
if (lastChar !== '"') return
let str = ''
let escaped = false
while (true) {
await next()
next()
if (escaped) {
if (lastChar === 'u') {
let unicode = ''
for (let i = 0; i < 4; i++) {
await next()
next()
if (!isHexDigit(lastChar)) {
throw new SyntaxError(`Invalid Unicode escape sequence '\\u${unicode}${lastChar}' at position ${pos}`)
}
@ -216,137 +240,137 @@ async function* parseJson() {
str += lastChar
}
}
await next()
next()
return str
}
async function parseNumber() {
function parseNumber() {
if (!isDigit(lastChar) && lastChar !== '-') return
let numStr = ''
if (lastChar === '-') {
numStr += lastChar
await next()
next()
if (!isDigit(lastChar)) {
throw new SyntaxError(`Invalid number format at position ${pos}.`)
}
}
if (lastChar === '0') {
numStr += lastChar
await next()
next()
} else {
while (isDigit(lastChar)) {
numStr += lastChar
await next()
next()
}
}
if (lastChar === '.') {
numStr += lastChar
await next()
next()
if (!isDigit(lastChar)) {
throw new SyntaxError(`Invalid number format at position ${pos}.`)
}
while (isDigit(lastChar)) {
numStr += lastChar
await next()
next()
}
}
if (lastChar === 'e' || lastChar === 'E') {
numStr += lastChar
await next()
next()
if (lastChar === '+' || lastChar === '-') {
numStr += lastChar
await next()
next()
}
if (!isDigit(lastChar)) {
throw new SyntaxError(`Invalid number format at position ${pos}.`)
}
while (isDigit(lastChar)) {
numStr += lastChar
await next()
next()
}
}
return isInteger(numStr) ? toSafeNumber(numStr) : parseFloat(numStr)
}
async function parseObject() {
function parseObject() {
if (lastChar !== '{') return
await next()
await skipWhitespace()
next()
skipWhitespace()
const obj = {}
if (lastChar === '}') {
await next()
next()
return obj
}
while (true) {
if (lastChar !== '"') {
throw new SyntaxError(`Unexpected character '${lastChar}' at position ${pos}. Expected a property name enclosed in double quotes.`)
throw new SyntaxError(errorSnippet())
}
const key = await parseString()
await skipWhitespace()
const key = parseString()
skipWhitespace()
if (lastChar !== ':') {
throw new SyntaxError(`Unexpected character '${lastChar}' at position ${pos}. Expected ':'.`)
}
await next()
const value = await parseValue()
next()
const value = parseValue()
expectValue(value)
obj[key] = value
await skipWhitespace()
skipWhitespace()
if (lastChar === '}') {
await next()
next()
return obj
} else if (lastChar === ',') {
await next()
await skipWhitespace()
next()
skipWhitespace()
} else {
throw new SyntaxError(`Unexpected character '${lastChar}' at position ${pos}. Expected ',' or '}'.`)
}
}
}
async function parseArray() {
function parseArray() {
if (lastChar !== '[') return
await next()
await skipWhitespace()
next()
skipWhitespace()
const array = []
if (lastChar === ']') {
await next()
next()
return array
}
while (true) {
const value = await parseValue()
const value = parseValue()
expectValue(value)
array.push(value)
await skipWhitespace()
skipWhitespace()
if (lastChar === ']') {
await next()
next()
return array
} else if (lastChar === ',') {
await next()
await skipWhitespace()
next()
skipWhitespace()
} else {
throw new SyntaxError(`Unexpected character '${lastChar}' at position ${pos}. Expected ',' or ']'.`)
}
}
}
async function parseKeyword(name, value) {
function parseKeyword(name, value) {
if (lastChar !== name[0]) return
for (let i = 1; i < name.length; i++) {
await next()
next()
if (lastChar !== name[i]) {
throw new SyntaxError(`Unexpected character '${lastChar}' at position ${pos}.`)
}
}
await next()
next()
if (isWhitespace(lastChar) || lastChar === ',' || lastChar === '}' || lastChar === ']' || lastChar === undefined) {
return value
}
throw new SyntaxError(`Unexpected character '${lastChar}' at position ${pos}.`)
}
async function skipWhitespace() {
function skipWhitespace() {
while (isWhitespace(lastChar)) {
await next()
next()
}
}
@ -375,26 +399,23 @@ async function* parseJson() {
function expectValue(value) {
if (value === undefined) {
throw new SyntaxError(`JSON value expected but got '${value}' at position ${pos}. ${printErrorSnippet()}`)
throw new SyntaxError(`JSON value expected but got '${value}' at position ${pos}. ${errorSnippet()}`)
}
}
}
function stringify(value, isPretty = false) {
const colors = {
key: isPretty ? '\x1b[1;34m' : '',
string: isPretty ? '\x1b[32m' : '',
number: isPretty ? '\x1b[33m' : '',
number: isPretty ? '\x1b[36m' : '',
boolean: isPretty ? '\x1b[35m' : '',
null: isPretty ? '\x1b[36m' : '',
null: isPretty ? '\x1b[2m' : '',
reset: isPretty ? '\x1b[0m' : '',
key: isPretty ? '\x1b[1m' : '',
brace: isPretty ? '\x1b[1m' : '',
}
const indent = 2
function getIndent(level) {
return ' '.repeat(indent * level)
return ' '.repeat(2 * level)
}
function stringifyValue(value, level = 0) {
@ -406,25 +427,21 @@ function stringify(value, isPretty = false) {
return `${colors.number}${value}${colors.reset}`
} else if (typeof value === 'boolean') {
return `${colors.boolean}${value}${colors.reset}`
} else if (value === null) {
} else if (value === null || typeof value === 'undefined') {
return `${colors.null}null${colors.reset}`
} else if (Array.isArray(value)) {
if (value.length === 0) {
return `${colors.brace}[]${colors.reset}`
return `[]`
}
const items = value
.map((v) => `${getIndent(level + 1)}${stringifyValue(v, level + 1)}`)
.join(',\n')
return `${colors.brace}[\n${items}\n${getIndent(level)}]${colors.reset}`
return `[\n${items}\n${getIndent(level)}]`
} else if (typeof value === 'object') {
const keys = Object.keys(value)
if (keys.length === 0) {
return `${colors.brace}{${colors.reset}}`
return `{}`
}
const entries = keys
.map(
(key) =>
@ -434,10 +451,8 @@ function stringify(value, isPretty = false) {
)}`
)
.join(',\n')
return `${colors.brace}{\n${entries}\n${getIndent(level)}}${colors.reset}`
return `{\n${entries}\n${getIndent(level)}}`
}
throw new Error(`Unsupported value type: ${typeof value}`)
}

@ -12,7 +12,7 @@
},
"repository": "antonmedv/fx",
"homepage": "https://fx.wtf",
"description": "Command-line JSON viewer",
"description": Command-line JSON viewer",
"author": "Anton Medvedev <anton@medv.io>",
"license": "MIT"
}

Loading…
Cancel
Save