Skip to content

Commit

Permalink
Merge pull request #34 from uzmoi/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
uzmoi authored Sep 21, 2024
2 parents 2c6bc0b + b34d4e4 commit df6ef00
Show file tree
Hide file tree
Showing 31 changed files with 3,482 additions and 4,768 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
cache: npm

- run: npm ci
- run: npm run coverage
- run: npm test -- --coverage

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
Expand Down
19 changes: 15 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@
# Change Log

## [Unreleased]
### Added
- Added alias `do_` for `qo`.
- Added `PerformOptions` type.

### Changed
## [0.10.0] - 2024-09-21

- Added `parseA` function.
- Added `sepBy` parser.
- Added alias `do_` for `qo`.
- Added `perform.option`, `perform.many` and `perform.while` in `qo`.
- Moved option to rollback state if `perform.try` fails to `PerformOptions`.
- Added defaultValue parameter to `perform.try`.
- Using `many` with a parser that does not consume elements no longer causes an infinite loop.
- `anyEl` changed to function.
- Renamed `anyEl`, `eoi`, `codePoint` and `anyChar` to camelCase.
- Deprecated `Parser.parse`, `Parser.and`, `Parser.andMap` and `Parser.or`.
- Removed `Parser.many`, `Parser.manyAccum` and `manyAccum`.

## [0.9.0] - 2024-01-16
### Added
- Added helper `Perform` type to specify source type in `qo` function.

### Changed
- Add source type parameter to `Parser`.
- `Source` type changed to helper that returns the input type of the parser.

## [0.8.0] - 2023-06-24
### Added
Expand Down
17 changes: 17 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"version": "0.2",
"useGitignore": true,
"ignorePaths": ["package.json", "package-lock.json"],
"words": [
"accum",
"biomejs",
"emnorst",
"fuga",
"hoge",
"parsea",
"parsimmon",
"tsup",
"uzmoi",
"vitest"
]
}
10 changes: 5 additions & 5 deletions examples/__snapshots__/script.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`script 1`] = `
{
"last": null,
"stmts": [
"statements": [
{
"body": {
"last": {
"elements": [],
"type": "Tuple",
},
"stmts": [
"statements": [
{
"expr": {
"expression": {
"arguments": [
{
"type": "String",
Expand All @@ -25,7 +25,7 @@ exports[`script 1`] = `
},
"type": "Call",
},
"type": "Expr",
"type": "Expression",
},
],
"type": "Block",
Expand Down
1 change: 1 addition & 0 deletions examples/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "vitest/importMeta";
41 changes: 0 additions & 41 deletions examples/json.test.ts

This file was deleted.

80 changes: 54 additions & 26 deletions examples/json.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
import type { JsonValue } from "emnorst";
import { EOI, type Parser, choice, el, lazy, literal, many, qo, regex } from "../src";

const sepBy = <T>(parser: Parser<T, string>, sep: Parser<unknown, string>) =>
qo<T[], string>(perform => {
const head = perform(parser);
const rest = perform(sep.then(parser).apply(many));
return [head, ...rest];
});
import * as P from "parsea";

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

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

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

Expand All @@ -32,11 +25,12 @@ const escapeTable = {
t: "\t",
};

const string = regex(/(?:\\(?:["\\/bfnrt]|u[0-9A-Fa-f]{4})|[^"\\])*/)
.between(el('"'))
// cspell:ignore bfnrt
const string = P.regex(/(?:\\(?:["\\/bfnrt]|u[0-9A-Fa-f]{4})|[^"\\])*/)
.between(P.el('"'))
.map(escapedString =>
escapedString.replace(/\\(u[0-9A-Fa-f]{4}|.)/g, (_, escape: string) => {
if (escape[0] === "u") {
if (escape.startsWith("u")) {
return String.fromCharCode(parseInt(escape.slice(1), 16));
}
if (escape in escapeTable) {
Expand All @@ -46,18 +40,52 @@ const string = regex(/(?:\\(?:["\\/bfnrt]|u[0-9A-Fa-f]{4})|[^"\\])*/)
}),
);

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

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

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

const keyValue = P.seq([string.between(ws).skip(P.el(":")), json]);

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

const keyValue = string.between(ws).skip(el(":")).and(jsonValue);
if (import.meta.vitest) {
const { test, expect } = import.meta.vitest;

const object = keyValue
.apply(sepBy, el(","))
.or(empty)
.between(el("{"), el("}"))
.map<Record<string, JsonValue>>(Object.fromEntries);
test.each([
" null ",
" [ ] ",
" [ null , 0 ] ",
" { } ",
` { "1" : null , "2" : 0 } `,
"0",
"3.141592",
"4.2195e1",
"0.00E+00",
"-0e-0",
])("%o", jsonString => {
expect(P.parseA(json, jsonString)).toEqual(JSON.parse(jsonString));
});

test.each(["00", "- 0", "0.", ".0"])("%o is invalid json.", n => {
expect(() => P.parseA(json, n)).toThrow(P.ParseAError);
});

export const jsonParser = jsonValue.skip(EOI);
test.each([
['"'],
["\\"],
["/"],
["b", "\b"],
["f", "\f"],
["n", "\n"],
["r", "\r"],
["t", "\t"],
["u1234", "\u1234"],
])("escape %s", (escapeChar, char = escapeChar) => {
expect(P.parseA(json, `"\\${escapeChar}"`)).toBe(char);
});
}
26 changes: 15 additions & 11 deletions examples/parser-by-do.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Config, type Parser, qo } from "../src";
import { type Config, type Parser, qo } from "parsea";

// For simplicity, the behavior may differ in a few cases.

Expand Down Expand Up @@ -36,29 +36,33 @@ const between = <T, S>(parser: Parser<T, S>, pre: Parser<unknown, S>, post = pre
const or = <T, U, S>(left: Parser<T, S>, right: Parser<U, S>) =>
qo<T | U, S>(perform => {
const [success, result] = perform.try(
[false] as const,
() => [true, perform(left)] as const,
[false],
);
return success ? result : perform(right);
});

const option = <T, U, S>(parser: Parser<T, S>, defaultValue: U) =>
qo<T | U, S>(perform => {
return perform.try(defaultValue, () => perform(parser));
});
qo<T | U, S>(perform => perform.option(parser, defaultValue));

const seq = <T, S>(parsers: readonly Parser<T, S>[]): Parser<T[], S> =>
qo(perform => parsers.map(parser => perform(parser)));

const many = <T, S>(parser: Parser<T, S>): Parser<T[], S> =>
qo(perform => {
return parsers.map(parser => perform(parser));
const result: T[] = [];
perform.while(() => {
result.push(perform(parser));
});
return result;
});

const many = <T, S>(parser: Parser<T, S>): Parser<T[], S> =>
const sepBy = <T, S>(parser: Parser<T, S>, sep: Parser<unknown, S>): Parser<T[], S> =>
qo(perform => {
const result: T[] = [];
perform.try(undefined, () => {
for (;;) {
result.push(perform(parser, { allowPartial: true }));
}
perform.while(() => {
result.push(perform(parser));
perform(sep, { allowPartial: true });
});
return result;
});
22 changes: 0 additions & 22 deletions examples/s-expression.test.ts

This file was deleted.

27 changes: 19 additions & 8 deletions examples/s-expression.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import { type Parser, choice, el, lazy, many, regex } from "../src";
import { type Parser, choice, el, lazy, many, parseA, regex } from "parsea";

export type SExpression = string | readonly SExpression[];

const list = lazy(() => SExpression)
.apply(many)
.between(el("("), el(")"));
const list = lazy(() => SExpression.apply(many).between(el("("), el(")")));

export const SExpression: Parser<SExpression, string> = choice([
el("'")
.option()
.and(list)
.map(([quote, list]) => (quote ? ["quote", ...list] : list)),
el("'").then(list.map(list => ["quote", ...list])),
list,
regex(/"([^"\\]|\\.)*"/),
regex(/[^\s()"]+/),
]).between(regex(/\s*/));

if (import.meta.vitest) {
const { test, expect } = import.meta.vitest;

test("Hello world!", () => {
expect(parseA(SExpression, '(print "Hello world!")')).toEqual([
"print",
'"Hello world!"',
]);
});

test("quote list", () => {
expect(parseA(SExpression, "'(1 2 3 4)")).toEqual(["quote", "1", "2", "3", "4"]);
});
}
Loading

0 comments on commit df6ef00

Please sign in to comment.