Skip to content

Commit

Permalink
Merge pull request #32 from uzmoi/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
uzmoi authored Jan 16, 2024
2 parents 9e4659e + a260b7a commit 6929431
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 51 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

## [Unreleased]

## [0.9.0] - 2024-01-16
### Changed
- Add source type parameter to `Parser`.

## [0.8.0] - 2023-06-24
### Added
- Added `Parser.prototype.apply`
Expand Down
4 changes: 2 additions & 2 deletions examples/__snapshots__/script.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
exports[`script 1`] = `
{
"last": null,
"stats": [
"stmts": [
{
"body": {
"last": {
"elements": [],
"type": "Tuple",
},
"stats": [
"stmts": [
{
"expr": {
"arguments": [
Expand Down
7 changes: 2 additions & 5 deletions examples/json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ describe("JSON", () => {
});
});

test("number parsing to fail..", () => {
expect(jsonParser.parse("00")).toHaveProperty("success", false);
expect(jsonParser.parse("- 0")).toHaveProperty("success", false);
expect(jsonParser.parse("0.")).toHaveProperty("success", false);
expect(jsonParser.parse(".0")).toHaveProperty("success", false);
test.each(["00", "- 0", "0.", ".0"])("%o is invalid json.", n => {
expect(jsonParser.parse(n)).toHaveProperty("success", false);
});

test.each([
Expand Down
22 changes: 22 additions & 0 deletions examples/s-expression.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { describe, expect, test } from "@jest/globals";
import { EOI } from "../src";
import { SExpression } from "./s-expression";

describe("S-expression", () => {
test("Hello world!", () => {
const source = '(print "Hello world!")';
expect(SExpression.skip(EOI).parse(source)).toEqual({
success: true,
index: expect.any(Number),
value: ["print", '"Hello world!"'],
});
});
test("quote list", () => {
const source = "'(1 2 3 4)";
expect(SExpression.skip(EOI).parse(source)).toEqual({
success: true,
index: expect.any(Number),
value: ["quote", "1", "2", "3", "4"],
});
});
});
16 changes: 16 additions & 0 deletions examples/s-expression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type Parser, choice, el, lazy, many, regex } from "../src";

export type SExpression = string | readonly SExpression[];

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)),
regex(/"([^"\\]|\\.)*"/),
regex(/[^\s()"]+/),
]).between(regex(/\s*/));
30 changes: 15 additions & 15 deletions examples/script.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { describe, expect, test } from "@jest/globals";
import { expr, stat } from "./script";
import { expr, stmt } from "./script";

describe("stat", () => {
describe("stmt", () => {
test("Let", () => {
const result = stat.parse("let hoge = 0;");
const result = stmt.parse("let hoge = 0;");
expect(result.success && result.value).toEqual({
type: "Let",
name: "hoge",
Expand All @@ -12,41 +12,41 @@ describe("stat", () => {
});

test("DefFn", () => {
const result = stat.parse("fn main(arg) {};");
const result = stmt.parse("fn main(arg) {};");
expect(result.success && result.value).toEqual({
type: "DefFn",
name: "main",
params: ["arg"],
body: { type: "Block", stats: [], last: null },
body: { type: "Block", stmts: [], last: null },
});
});

test("Return", () => {
const result = stat.parse("return;");
const result = stmt.parse("return;");
expect(result.success && result.value).toEqual({
type: "Return",
body: null,
});
});

test("While", () => {
const result = stat.parse("while (false) {};");
const result = stmt.parse("while (false) {};");
expect(result.success && result.value).toEqual({
type: "While",
test: { type: "Bool", value: false },
body: { type: "Block", stats: [], last: null },
body: { type: "Block", stmts: [], last: null },
});
});

test("Break", () => {
const result = stat.parse("break;");
const result = stmt.parse("break;");
expect(result.success && result.value).toEqual({
type: "Break",
});
});

test("Expr", () => {
const result = stat.parse("0;");
const result = stmt.parse("0;");
expect(result.success && result.value).toEqual({
type: "Expr",
expr: { type: "Number", value: 0 },
Expand Down Expand Up @@ -127,23 +127,23 @@ describe("expr", () => {
const result = expr.parse("{}");
expect(result.success && result.value).toEqual({
type: "Block",
stats: [],
stmts: [],
last: null,
});
});
test("stats", () => {
test("stmts", () => {
const result = expr.parse("{ 0; }");
expect(result.success && result.value).toEqual({
type: "Block",
stats: [{ type: "Expr", expr: { type: "Number", value: 0 } }],
stmts: [{ type: "Expr", expr: { type: "Number", value: 0 } }],
last: null,
});
});
test("expr", () => {
const result = expr.parse("{ 0 }");
expect(result.success && result.value).toEqual({
type: "Block",
stats: [],
stmts: [],
last: { type: "Number", value: 0 },
});
});
Expand All @@ -155,7 +155,7 @@ describe("expr", () => {
expect(result.success && result.value).toEqual({
type: "If",
test: { type: "Bool", value: true },
then: { type: "Block", stats: [], last: null },
then: { type: "Block", stmts: [], last: null },
else: null,
});
});
Expand Down
24 changes: 12 additions & 12 deletions examples/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export type Expr =
| { type: "Number"; value: number }
| { type: "String"; value: string }
| { type: "Tuple"; elements: readonly Expr[] }
| { type: "Block"; stats: Stat[]; last: Expr | null }
| { type: "Block"; stmts: Stmt[]; last: Expr | null }
| { type: "If"; test: Expr; then: Expr; else: Expr | null }
| { type: "Ident"; name: string }
| { type: "Call"; callee: Expr; arguments: readonly Expr[] }
Expand Down Expand Up @@ -39,7 +39,7 @@ const keyword = (keyword: string): P.Parser<unknown, string> => {

const Ident = P.regex(/\w+/).map(name => ({ type: "Ident", name }) satisfies Expr);

export type Stat =
export type Stmt =
| { type: "Let"; name: string; init: Expr }
| { type: "DefFn"; name: string; params: readonly string[]; body: Expr }
| { type: "Return"; body: Expr | null }
Expand All @@ -50,7 +50,7 @@ export type Stat =
const Let = keyword("let")
.then(Ident.between(ws))
.skip(P.el("="))
.andMap(expr, ({ name }, init): Stat => ({ type: "Let", name, init }));
.andMap(expr, ({ name }, init): Stmt => ({ type: "Let", name, init }));

const DefFn = P.seq([
keyword("fn").then(Ident.between(ws)),
Expand All @@ -60,7 +60,7 @@ const DefFn = P.seq([
.between(P.el("("), P.el(")"))
.map(nodes => nodes.map(node => node.name)),
expr,
]).map<Stat>(([{ name }, params, body]) => ({
]).map<Stmt>(([{ name }, params, body]) => ({
type: "DefFn",
name,
params,
Expand All @@ -70,18 +70,18 @@ const DefFn = P.seq([
const Return = keyword("return")
.then(expr.option(null))
.skip(ws)
.map<Stat>(body => ({ type: "Return", body }));
.map<Stmt>(body => ({ type: "Return", body }));

const While = keyword("while")
.skip(ws)
.then(expr.between(P.el("("), P.el(")")))
.andMap(expr, (test, body): Stat => ({ type: "While", test, body }));
.andMap(expr, (test, body): Stmt => ({ type: "While", test, body }));

const Break = keyword("break").return<Stat>({ type: "Break" }).skip(ws);
const Break = keyword("break").return<Stmt>({ type: "Break" }).skip(ws);

const Expr = expr.map<Stat>(expr => ({ type: "Expr", expr }));
const Expr = expr.map<Stmt>(expr => ({ type: "Expr", expr }));

export const stat: P.Parser<Stat, string> = P.choice([
export const stmt: P.Parser<Stmt, string> = P.choice([
Let,
DefFn,
Return,
Expand Down Expand Up @@ -126,10 +126,10 @@ const Tuple = expr
.between(P.el("("), P.el(")"))
.map(elements => ({ type: "Tuple", elements }) satisfies Expr);

const Block = stat
const Block = stmt
.apply(P.many)
.andMap(expr.option(null), (stats, last) => {
return { type: "Block", stats, last } satisfies Expr;
.andMap(expr.option(null), (stmts, last) => {
return { type: "Block", stmts, last } satisfies Expr;
})
.skip(ws)
.between(P.el("{"), P.el("}"));
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "parsea",
"version": "0.8.0",
"version": "0.9.0",
"description": "parser combinator library for parsing ArrayLike.",
"type": "module",
"main": "dist/index.cjs",
Expand Down
10 changes: 4 additions & 6 deletions src/combinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ import { type ParseState, updateState } from "./state";
* Delays variable references until the parser runs.
*/
export const lazy = <T, S>(getParser: () => Parser<T, S>): Parser<T, S> => {
let parser: Parser<T, S>;
return new Parser((state, context) => {
if (parser == null) {
parser = getParser();
}
return parser.run(state, context);
const lazyParser: Parser<T, S> = new Parser((state, context) => {
// @ts-expect-error readonly
return (lazyParser.run = getParser().run)(state, context);
});
return lazyParser;
};

export const notFollowedBy = <S>(parser: Parser<unknown, S>): Parser<unknown, S> =>
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from "./combinator";
export * from "./do";
export type { Config, ParseResult, Parsed, Parser } from "./internal";
export type { Config, ParseResult, Parsed, Parser, Source } from "./internal";
export * from "./primitive";
export * from "./string";
20 changes: 13 additions & 7 deletions src/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,19 @@ export const ANY_CHAR = /* @__PURE__ */ new Parser<string, string>((state, conte
});

export const regexGroup = (re: RegExp): Parser<RegExpExecArray, string> => {
const fixedRegex = new RegExp(`^(?:${re.source})`, re.flags.replace("g", ""));
let flags = re.flags.replace("g", "");
if (!re.sticky) {
flags += "y";
}
const fixedRegex = new RegExp(re, flags);

return new Parser((state, context) => {
if (typeof context.src !== "string") {
context.addError(state.i);
return null;
}
const matchResult = fixedRegex.exec(context.src.slice(state.i));
fixedRegex.lastIndex = state.i;
const matchResult = fixedRegex.exec(context.src);
if (matchResult === null) {
context.addError(state.i);
return null;
Expand All @@ -135,10 +140,11 @@ export const regex: {
defaultValue: T,
): Parser<string | T, string>;
} = (re: RegExp, groupId: number | string = 0, defaultValue?: undefined) =>
regexGroup(re).map(
matchResult =>
(typeof groupId === "number"
regexGroup(re).map(matchResult => {
const groupValue =
typeof groupId === "number"
? matchResult[groupId]
: // biome-ignore lint/style/noNonNullAssertion: overrideのため
matchResult.groups?.[groupId]!) ?? defaultValue,
);
matchResult.groups?.[groupId]!;
return groupValue ?? defaultValue;
});

0 comments on commit 6929431

Please sign in to comment.