Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #32

Merged
merged 10 commits into from
Jan 16, 2024
Merged

Dev #32

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
});