Skip to content

Instantly share code, notes, and snippets.

@xyhp915
Forked from Lucifier129/json-parser.js
Created November 10, 2019 08:54
Show Gist options
  • Save xyhp915/dc772db837c8b07c56cc3e642b8c7466 to your computer and use it in GitHub Desktop.
Save xyhp915/dc772db837c8b07c56cc3e642b8c7466 to your computer and use it in GitHub Desktop.
const first = source => {
if (!source) return 'empty source'
return [source[0], source.slice(1)]
}
// console.log('first', first('abc'), first(''))
const inject = value => source => [value, source]
// console.log('inject', inject(1)('123'), inject(2)(''))
const isFailed = result => typeof result === 'string'
const map = (parser, f) => source => {
let result = parser(source)
if (isFailed(result)) return result
let [data, rest_source] = result
return [f(data), rest_source]
}
// console.log('map', map(inject(1), n => n + 3)('test'))
const apply = (parserFn, parserArg) => source => {
let resultFn = parserFn(source)
if (isFailed(resultFn)) return resultFn
let [fn, rest_source_fn] = resultFn
let resultArg = parserArg(rest_source_fn)
if (isFailed(resultArg)) return resultArg
let [arg, rest_source_arg] = resultArg
return [fn(arg), rest_source_arg]
}
// console.log('apply', apply(inject(n => n + 1), inject(1))('123'))
const applyAll = (...args) => args.reduce(apply)
// console.log(
// 'applyAll',
// applyAll(inject(x => y => x + y), inject(1), inject(2))('123')
// )
const satisfy = predicate => source => {
let result = first(source)
if (isFailed(result)) return result
let [char, rest_source] = result
if (!predicate(char)) return `Unexpect char ${char}`
return [char, rest_source]
}
// console.log(
// 'satisfy',
// satisfy(x => x === '1')('123'),
// satisfy(x => x === '1')('423')
// )
const digit = satisfy(c => c >= '0' && c <= '9')
const lower = satisfy(c => c >= 'a' && c <= 'z')
const uppper = satisfy(c => c >= 'A' && c <= 'Z')
const char = target => satisfy(c => c === target)
const not = x => satisfy(value => value !== x)
// console.log('digit', digit('1'), digit('a'), digit('A'))
// console.log('lower', lower('1'), lower('a'), lower('A'))
// console.log('uppper', uppper('1'), uppper('a'), uppper('A'))
// console.log('char', char('0')('0'), char('0')('1'))
// console.log('not', not('1')('123'), not('1')('abc'))
const either = (parserA, parserB) => source => {
let resultA = parserA(source)
if (!isFailed(resultA)) return resultA
return parserB(source)
}
// console.log('either', either(char('0'), char('1'))('123'))
const oneOf = (...args) => args.reduce(either)
// console.log('oneOf', oneOf(char('0'), char('1'), char('2'))('123'))
const whiteSpace = oneOf(char(' '), char('s'), char('\n'), char('\t'))
// console.log('whiteSpace', whiteSpace(' '), whiteSpace('\n'), whiteSpace('\t'), whiteSpace('a'))
const many = parser => {
let concat = applyAll(
inject(item => list => [item, ...list]),
parser,
source => many(parser)(source)
)
return either(concat, inject([]))
}
// console.log('many', many(digit)('123abc'), many(digit)('abc123'))
const many1 = parser => source => {
let result = many(parser)(source)
if (isFailed(result)) return result
let [list, rest_source] = result
if (list.length === 0) return `At least match once`
return [list, rest_source]
}
// console.log('many1', many1(digit)('123abc'), many1(digit)('abc123'))
const string = str => {
if (str.length === 0) return inject('')
return applyAll(inject(x => xs => x + xs), char(str[0]), string(str.slice(1)))
}
// console.log('string', string('123')('1234'), string('123')('abc'))
const letter = either(lower, uppper)
const positiveInteger = map(many1(digit), list => Number(list.join('')))
const negativeInteger = applyAll(
inject(_ => x => -x),
char('-'),
positiveInteger
)
const integer = either(positiveInteger, negativeInteger)
const positiveFloat = applyAll(
inject(a => b => c => Number(a + b + c)),
map(many1(digit), list => list.join('')),
char('.'),
map(many1(digit), list => list.join(''))
)
const negativeFloat = applyAll(inject(_ => x => -x), char('-'), positiveFloat)
const float = either(positiveFloat, negativeFloat)
const number = either(float, integer)
const ignoreFirst = (first, second) =>
applyAll(inject(_ => value => value), first, second)
const separateBy = (parser, separator) =>
applyAll(
inject(item => list => [item, ...list]),
parser,
many(ignoreFirst(separator, parser))
)
const bracket = (open, parser, close) =>
applyAll(inject(_1 => x => _2 => x), open, parser, close)
const aroundBy = (parser, surround) =>
applyAll(inject(_1 => x => _2 => x), many(surround), parser, many(surround))
const aroundBySpace = parser => aroundBy(parser, whiteSpace)
const jValue = source =>
oneOf(jNull, jBoolean, jNumber, jString, jArray, jObject)(source)
const jNull = map(string('null'), () => null)
const jNumber = number
const jBoolean = map(
either(string('false'), string('true')),
result => result === 'true'
)
const jString = map(bracket(char('"'), many(not('"')), char('"')), list =>
list.join('')
)
const jArray = bracket(
aroundBySpace(char('[')),
separateBy(aroundBySpace(jValue), aroundBySpace(char(','))),
aroundBySpace(char(']'))
)
const jKey = jString
const jPair = applyAll(
inject(key => _ => value => [key, value]),
jKey,
aroundBySpace(char(':')),
jValue
)
const jPairs = separateBy(jPair, aroundBySpace(char(',')))
const jObject = map(
bracket(
aroundBySpace(char('{')),
either(aroundBySpace(jPairs), inject([])),
aroundBySpace(char('}'))
),
entries =>
entries.reduce((obj, [key, value]) => ({ ...obj, [key]: value }), {})
)
console.log('null', jValue('null'))
console.log('string', jString('""'), jString('"123123"'))
console.log(
'number',
jNumber('0'),
jNumber('123'),
jNumber('-123'),
jNumber('0.11'),
jNumber('-0.11')
)
console.log('array', jArray('[ 1, 2, 3, "123", [4, 5, 6] ]'))
console.log('object', jObject('{}'), jObject(`{ "a": 1 }`))
let result = jValue(
JSON.stringify({
a: 1,
b: 2,
c: [
1,
2,
{
d: 3
}
]
})
)
console.log('json', JSON.stringify(result, null, 2))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment