Skip to content

Commit

Permalink
feat: conditional rules in the language grammars
Browse files Browse the repository at this point in the history
  • Loading branch information
aradzie committed Oct 22, 2024
1 parent 836602a commit 337e83c
Show file tree
Hide file tree
Showing 22 changed files with 656 additions and 320 deletions.
12 changes: 11 additions & 1 deletion packages/keybr-code/lib/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ export type HasCodePoints = {
codePoints?: ReadonlySet<number>;
};

export type Prod = Span | Opt | Seq | Alt | Ref | string;
export type Prod = Cond | Span | Opt | Seq | Alt | Ref | string;

export type Cond = HasCodePoints & {
readonly flag: string;
readonly inv: boolean;
readonly cond: Prod;
};

export type Span = HasCodePoints & {
readonly cls: string;
Expand All @@ -31,6 +37,10 @@ export type Ref = HasCodePoints & {
readonly ref: string;
};

export function isCond(v: Prod): v is Cond {
return typeof v === "object" && "cond" in v;
}

export function isSpan(v: Prod): v is Span {
return typeof v === "object" && "span" in v;
}
Expand Down
6 changes: 2 additions & 4 deletions packages/keybr-code/lib/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@

import { readdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, join, resolve } from "node:path";
import { compiler } from "peggy";
import peggy from "peggy";
import { optimize } from "./optimize.ts";
import { parse } from "./parse.ts";
import { validate } from "./validate.ts";

if (compiler == null) {
/* Suppress the unused dependency warning. */
}
console.log(`Peggy version: ${peggy.VERSION}`);

export const rootDir = resolve(import.meta.dirname, "..");

Expand Down
5 changes: 3 additions & 2 deletions packages/keybr-code/lib/example.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#!/usr/bin/env -S node --import @keybr/tsl

import { LCG } from "@keybr/rand";
import { flagSet } from "./flags.ts";
import { Syntax } from "./syntax.ts";

for (const syntax of Syntax.ALL) {
console.log(`=== ${syntax.name} ===`);
console.log(`=== ${syntax.name} (${[...syntax.flags].join(",")}) ===`);
const rng = LCG(1);
for (let i = 0; i < 5; i++) {
console.log(syntax.generate(rng));
console.log(syntax.generate(flagSet(["*"]), rng));
}
console.log();
}
12 changes: 12 additions & 0 deletions packages/keybr-code/lib/find-flags.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import test from "ava";
import { findFlags } from "./find-flags.ts";
import { parse } from "./parse.ts";

test("find flags", (t) => {
const rules = parse(
`a -> { :if(f1) "a" { :if(f2) "b" | { :if(f3) "c" } } };\n` +
`b -> "..." { :class(c1) { :if(xyz) "1" | "2" | "3" } } "...";\n`,
);
const flags = findFlags(rules);
t.deepEqual(flags, new Set(["f1", "f2", "f3", "xyz"]));
});
49 changes: 49 additions & 0 deletions packages/keybr-code/lib/find-flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {
isAlt,
isCond,
isOpt,
isSeq,
isSpan,
type Prod,
type Rules,
} from "./ast.ts";

export function findFlags(rules: Rules): Set<string> {
const flags = new Set<string>();
for (const rule of Object.values(rules)) {
visit(rule);
}
return flags;

function visit(p: Prod): void {
if (isCond(p)) {
flags.add(p.flag);
visit(p.cond);
return;
}

if (isSpan(p)) {
visit(p.span);
return;
}

if (isOpt(p)) {
visit(p.opt);
return;
}

if (isSeq(p)) {
for (const child of p.seq) {
visit(child);
}
return;
}

if (isAlt(p)) {
for (const child of p.alt) {
visit(child);
}
return;
}
}
}
12 changes: 12 additions & 0 deletions packages/keybr-code/lib/flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export type Flags = {
has(flag: string): boolean;
};

export function flagSet(flags: readonly string[] | ReadonlySet<string>): Flags {
const set = new Set(flags);
return {
has(flag) {
return set.has(flag) || set.has("*");
},
};
}
14 changes: 13 additions & 1 deletion packages/keybr-code/lib/generate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LCG, type RNG } from "@keybr/rand";
import {
isAlt,
isCond,
isLit,
isOpt,
isRef,
Expand All @@ -9,6 +10,7 @@ import {
type Prod,
type Rules,
} from "./ast.ts";
import { type Flags, flagSet } from "./flags.ts";
import { Output } from "./output.ts";

const lcg = LCG(1);
Expand All @@ -18,11 +20,14 @@ const lcg = LCG(1);
*/
export function generate(
rules: Rules,
start: string = "start",
{
start = "start",
flags = flagSet(["*"]),
output = new Output(),
rng = lcg,
}: {
readonly start?: string;
readonly flags?: Flags;
readonly output?: Output;
readonly rng?: RNG;
} = {},
Expand All @@ -32,6 +37,13 @@ export function generate(
return String(output);

function visit(p: Prod): void {
if (isCond(p)) {
if (flags.has(p.flag) !== p.inv) {
visit(p.cond);
}
return;
}

if (isSpan(p)) {
visit(p.span);
return;
Expand Down
1 change: 1 addition & 0 deletions packages/keybr-code/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./flags.ts";
export * from "./generate.ts";
export * from "./syntax.ts";
16 changes: 12 additions & 4 deletions packages/keybr-code/lib/optimize.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
type Alt,
type Cond,
isAlt,
isCond,
isLit,
isOpt,
isSeq,
Expand Down Expand Up @@ -29,6 +31,10 @@ export function optimize(rules: Rules): Rules {
}

function visit(p: Prod): Prod {
if (isCond(p)) {
return visitCond(p);
}

if (isSpan(p)) {
return visitSpan(p);
}
Expand All @@ -48,13 +54,15 @@ function visit(p: Prod): Prod {
return p;
}

function visitSpan(v: Span): Prod {
const { cls, span } = v;
function visitCond({ cond, flag, inv }: Cond): Prod {
return { flag, inv, cond: visit(cond) };
}

function visitSpan({ span, cls }: Span): Prod {
return { cls, span: visit(span) };
}

function visitOpt(v: Opt): Prod {
const { f, opt } = v;
function visitOpt({ f, opt }: Opt): Prod {
if (f === 1) {
return visit(opt);
} else {
Expand Down
26 changes: 24 additions & 2 deletions packages/keybr-code/lib/parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,30 @@ test("parse alt", (t) => {
});
});

test("parse cond", (t) => {
t.deepEqual(parse(`start -> { :if(f1) "a" "b" "c" };`), {
start: { flag: "f1", inv: false, cond: { seq: ["a", "b", "c"] } },
});
t.deepEqual(parse(`start -> { :if(f1) "a" | "b" | "c" };`), {
start: { flag: "f1", inv: false, cond: { alt: ["a", "b", "c"] } },
});
t.deepEqual(parse(`start -> { :if(!f1) "a" "b" "c" };`), {
start: { flag: "f1", inv: true, cond: { seq: ["a", "b", "c"] } },
});
t.deepEqual(parse(`start -> { :if(!f1) "a" | "b" | "c" };`), {
start: { flag: "f1", inv: true, cond: { alt: ["a", "b", "c"] } },
});
});

test("parse span", (t) => {
t.deepEqual(parse(`start -> { :class(c1) "a" "b" "c" };`), {
start: { cls: "c1", span: { seq: ["a", "b", "c"] } },
});
t.deepEqual(parse(`start -> { :class(c1) "a" | "b" | "c" };`), {
start: { cls: "c1", span: { alt: ["a", "b", "c"] } },
});
});

test("priorities", (t) => {
t.deepEqual(parse(`start -> ("a" | "b") "c";`), {
start: { seq: [{ alt: ["a", "b"] }, "c"] },
Expand Down Expand Up @@ -70,8 +94,6 @@ test("syntax error", (t) => {
},
{
name: "SyntaxError",
message:
'Expected "(", "[", literal, ref, separator, or whitespace but end of input found.',
},
) as SyntaxError;
t.deepEqual(location, {
Expand Down
Loading

0 comments on commit 337e83c

Please sign in to comment.