Skip to content

Instantly share code, notes, and snippets.

@susisu
Created November 5, 2014 15:20
Show Gist options
  • Save susisu/91ef172b7ed1a8820fe1 to your computer and use it in GitHub Desktop.
Save susisu/91ef172b7ed1a8820fe1 to your computer and use it in GitHub Desktop.
Simple JSON Parser with Loquat https://github.com/susisu/Loquat
/*
* Simple JSON Parser
* http://www.json.org/index.html
*/
var lq = require("loquat");
var util = require("util");
/*
* Parsers
*/
var json_symbol_colon = lexeme(lq.char(":")).label("colon");
var json_symbol_comma = lexeme(lq.char(",")).label("comma");
var json_symbol_open_brace = lexeme(lq.char("{")).label("open brace");
var json_symbol_close_brace = lexeme(lq.char("}")).label("close brace");
var json_symbol_open_bracket = lexeme(lq.char("[")).label("open bracket");
var json_symbol_close_bracket = lexeme(lq.char("]")).label("close bracket");
var json_value = new lq.LazyParser(function () {
return lq.choice([
json_string,
json_number,
json_object,
json_array,
json_true,
json_false,
json_null
]);
})
.label("value");
var json_string = lexeme(
lq.manyChar(
lq.choice([
lq.noneOf("\"\\\b\f\n\r\t"),
lq.char("\\").then(
lq.fmap(unescapeControlChar)(lq.oneOf("\"\\/bnfrt")),
lq.fmap(unescapeUnicodeChar)(
lq.char("u").then(lq.fmap(join)(lq.hexDigit.count(4)))
)
)
])
)
.between(lq.char("\""), lq.char("\""))
)
.label("string");
var json_number = lexeme(
lq.fmap(parseFloat)(
lq.fmap(join)(
lq.sequence([
lq.option("", lq.char("-")),
lq.choice([
lq.char("0"),
lq.fmap(join)(
lq.sequence([
lq.oneOf("123456789"),
lq.manyChar(lq.digit)
])
)
]),
lq.option(
"",
lq.fmap(join)(
lq.sequence([
lq.char("."),
lq.manyChar1(lq.digit)
])
)
),
lq.option(
"",
lq.fmap(join)(
lq.sequence([
lq.oneOf("eE"),
lq.option("", lq.oneOf("+-")),
lq.manyChar1(lq.digit)
])
)
)
])
)
)
)
.label("number");
var json_object = lq.sequence([json_string, json_symbol_colon, json_value])
.sepBy(json_symbol_comma)
.between(json_symbol_open_brace, json_symbol_close_brace)
.bind(function (key_values) {
var obj = {};
for (var i = 0; i < key_values.length; i ++) {
obj[key_values[i][0]] = key_values[i][2];
}
return lq.pure(obj);
})
.label("object");
var json_array = json_value
.sepBy(json_symbol_comma)
.between(json_symbol_open_bracket, json_symbol_close_bracket)
.label("array");
var json_true = lexeme(lq.string("true").try().then(lq.pure(true))).label("true");
var json_false = lexeme(lq.string("false").try().then(lq.pure(false))).label("false");
var json_null = lexeme(lq.string("null").try().then(lq.pure(null))).label("null");
/*
* Utility Functions
*/
function lexeme (parser) {
return parser.left(lq.spaces);
}
function join (array) {
return array.join("");
}
function unescapeControlChar (char) {
switch (char) {
case "\"": return "\"";
case "\\": return "\\";
case "/" : return "/";
case "b" : return "\b";
case "n" : return "\n";
case "f" : return "\f";
case "r" : return "\r";
case "t" : return "\t";
default : return char;
}
}
function unescapeUnicodeChar (codeStr) {
return String.fromCharCode(parseInt(codeStr, 16));
}
/*
* Main
*/
var parser = lq.spaces.then(json_value).left(lq.eof);
process.stdin.setEncoding("utf8");
process.stdout.write("> ");
process.stdin.on("readable", function () {
var chunk = process.stdin.read();
if (chunk !== null) {
try {
console.time("parse");
var result = lq.parse(parser, "", chunk);
console.timeEnd("parse");
if (result.succeeded) {
process.stdout.write(
util.inspect(result.value, { colors: true }) + "\n"
);
}
else {
process.stdout.write(lq.show(result.error) + "\n");
}
process.stdout.write("> ");
}
catch (error) {
console.error(error);
}
}
});
process.stdin.on("end", function() {
process.stdout.write("\nbye\n");
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment