Skip to content

Commit

Permalink
Merge pull request #14 from rizzzse/dev/example
Browse files Browse the repository at this point in the history
Dev/example
  • Loading branch information
uzmoi authored Sep 17, 2021
2 parents f0b5c4e + 1ca558c commit f77904c
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 1 deletion.
71 changes: 71 additions & 0 deletions examples/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { JsonValue } from "emnorst";
import { choice, el, eoi, lazy, literal, Parser, qo, regex, seq } from "../src";

const sepBy = <T>(parser: Parser<T>, sep: Parser<unknown>) =>
qo(perform => {
const head = perform(parser);
const rest = perform(sep.right(parser).many());
return [head, ...rest];
});

const between = <T>(
parser: Parser<T>,
open: Parser<unknown>,
close: Parser<unknown> = open,
): Parser<T> =>
qo(perform => {
perform(open);
const val = perform(parser);
perform(close);
return val;
});

// https://www.json.org/

const ws = regex(/[ \n\r\t]*/);

const jsonValue: Parser<JsonValue> = lazy(() =>
between(choice<JsonValue>([
object,
array,
string,
number,
literal("true").map(() => true),
literal("false").map(() => false),
literal("null").map(() => null),
]), ws),
);

const escapeTable = {
b: "\b",
f: "\f",
n: "\n",
r: "\r",
t: "\t",
};

const string = between(regex(/(?:\\(?:["\\/bfnrt]|u[0-9A-Fa-f]{4})|[^"\\])*/), el("\""))
.map(escapedString =>
escapedString.replace(/\\(u[0-9A-Fa-f]{4}|.)/g, (_, escape: string) => {
if(escape[0] === "u") {
return String.fromCharCode(parseInt(escape.slice(1), 16));
}
if(escape in escapeTable) {
return escapeTable[escape as keyof typeof escapeTable];
}
return escape;
}),
);

const number = regex(/-?(0|[1-9]\d*)(.\d+)?([Ee][-+]?\d+)?/).map(Number);

const empty = ws.map<[]>(() => []);

const array = between(sepBy(jsonValue, el(",")).or(empty), el("["), el("]"));

const keyValue = seq([ws.right(string), ws.right(el(":")).right(jsonValue)]);

const object = between(sepBy(keyValue, el(",")).or(empty), el("{"), el("}"))
.map<Record<string, JsonValue>>(Object.fromEntries);

export const jsonParser = jsonValue.left(eoi);
28 changes: 28 additions & 0 deletions examples/parser-by-do.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Parser, qo } from "../src";

const pure = <T>(val: T) => qo(() => val);

const map = <T, U>(parser: Parser<T>, f: (v: T) => U) =>
qo(perform => f(perform(parser)));

const flatMap = <T, U>(parser: Parser<T>, f: (v: T) => Parser<U>) =>
qo(perform => perform(f(perform(parser))));

const right = <T>(left: Parser<unknown>, right: Parser<T>) =>
qo(perform => (perform(left), perform(right)));

const left = <T>(left: Parser<T>, right: Parser<unknown>) =>
qo(perform => {
const leftVal = perform(left);
perform(right);
return leftVal;
});

const or = <T, U>(left: Parser<T>, right: Parser<U>) =>
qo(perform => {
try {
return perform(left);
} catch(err) {
return perform(right);
}
});
42 changes: 42 additions & 0 deletions tests/examples.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { jsonParser } from "../examples/json";

describe("JSON", () => {
test("ignore whitespace.", () => {
expect(jsonParser).parseToSucc(" null ", 6, null);
expect(jsonParser).parseToSucc(" [ ] ", 5, []);
expect(jsonParser).parseToSucc(" [ null , 0 ] ", 14, [null, 0]);
expect(jsonParser).parseToSucc(` { } `, 5, {});
expect(jsonParser).parseToSucc(` { "1" : null , "2" : 0 } `, 26, { 1: null, 2: 0 });
});
test.each([
"0",
"3.141592",
"4.2195e1",
"0.00E+00",
"0e-0",
])("parse numbers.", json => {
expect(jsonParser).parseToSucc(json, json.length, JSON.parse(json));
expect(jsonParser).parseToSucc("-" + json, json.length + 1, -JSON.parse(json));
});
test("number parsing to fail..", () => {
expect(jsonParser).parseToFail("00", 1);
expect(jsonParser).parseToFail("- 0", 0);
expect(jsonParser).parseToFail("0.", 1);
expect(jsonParser).parseToFail(".0", 0);
});
test.each([
["\""],
["\\"],
["/"],
["b", "\b"],
["f", "\f"],
["n", "\n"],
["r", "\r"],
["t", "\t"],
])("escape %s", (escapeChar, char = escapeChar) => {
expect(jsonParser).parseToSucc(`"\\${escapeChar}"`, 4, char);
});
test("escape unicode", () => {
expect(jsonParser).parseToSucc(`"\\u1234"`, 8, "\u1234");
});
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"moduleResolution": "node",
"esModuleInterop": true,
"module": "ES2015",
"target": "ES2018",
"target": "ES2020",
"strict": true,
"importsNotUsedAsValues": "error",
"declaration": true,
Expand Down

0 comments on commit f77904c

Please sign in to comment.