Skip to content

Commit

Permalink
lvs: UserFn translate to ESM
Browse files Browse the repository at this point in the history
  • Loading branch information
yoursunny committed Oct 25, 2024
1 parent 4e0422c commit 2d59f68
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 26 deletions.
2 changes: 1 addition & 1 deletion integ/browser-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"devDependencies": {
"@nodelib/fs.walk": "^2.0.0",
"@types/webpack": "^5.28.5",
"@zenfs/core": "1.1.1",
"@zenfs/core": "1.1.2",
"@zenfs/dom": "1.0.1",
"fork-ts-checker-webpack-plugin": "^9.0.2",
"html-webpack-plugin": "^5.6.3",
Expand Down
2 changes: 1 addition & 1 deletion pkg/fileserver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@ndn/segmented-object": "workspace:*",
"@ndn/tlv": "workspace:*",
"@ndn/util": "workspace:*",
"@zenfs/core": "1.1.1",
"@zenfs/core": "1.1.2",
"mnemonist": "^0.39.8",
"obliterator": "^2.0.4",
"streaming-iterables": "^8.0.1",
Expand Down
2 changes: 1 addition & 1 deletion pkg/lvs/src/tlv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ buildUserFnArg.subclass = UserFnArg;
makeDiscriminatedUnion(buildUserFnArg);

const buildUserFnCall = new StructBuilder("UserFnCall", TT.UserFnCall)
.add(TT.UserFnId, "fn", StructFieldText)
.add(TT.UserFnId, "fn", StructFieldText, { required: true })
.add(TT.FnArgs, "args", StructFieldType.wrap(UserFnArg), { repeat: true });
export class UserFnCall extends buildUserFnCall.baseClass<UserFnCall>() {}
buildUserFnCall.subclass = UserFnCall;
Expand Down
84 changes: 74 additions & 10 deletions pkg/lvs/src/translate.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
import { type Component, Name } from "@ndn/packet";
import { pattern as P, TrustSchemaPolicy } from "@ndn/trust-schema";
import { pattern as P, type printESM, TrustSchemaPolicy } from "@ndn/trust-schema";
import { assert } from "@ndn/util";

import { type ConsOption, type Constraint, type LvsModel, type Node, type PatternEdge, ValueEdge } from "./tlv";
import { type ConsOption, type Constraint, type LvsModel, type Node, type PatternEdge, type UserFnCall, ValueEdge } from "./tlv";

export function toPolicy(model: LvsModel): TrustSchemaPolicy {
return new Translator(model).translate();
export function toPolicy(model: LvsModel, vtable: VtableInput = {}): TrustSchemaPolicy {
vtable = vtable instanceof Map ? vtable : new Map(Object.entries(vtable));
const translator = new Translator(model, vtable);
translator.translate();
return translator.policy;
}

export type UserFn = (value: Component, args: readonly Component[]) => boolean;
export type Vtable = ReadonlyMap<string, UserFn>;
export type VtableInput = Vtable | Record<string, UserFn>;

class Translator {
constructor(private readonly model: LvsModel) {}
constructor(
private readonly model: LvsModel,
private readonly vtable: Vtable,
) {}

private readonly policy = new TrustSchemaPolicy();
public readonly policy = new TrustSchemaPolicy();
private readonly tagSymbols = new Map<number, string>();
private readonly wantedNodes = new Set<number>();
public readonly neededFns = new Set<string>();
private lastAutoId = 0;

public translate(): TrustSchemaPolicy {
public translate(): void {
this.gatherTagSymbols();
this.gatherNodes();
this.processPatterns();
this.processRules();
return this.policy;
}

private gatherTagSymbols(): void {
Expand Down Expand Up @@ -99,18 +109,32 @@ class Translator {
if (co.value) {
return this.trValue(co.value);
}

if (co.tag) {
return new P.VariablePattern(this.nameTag(co.tag));
}

assert(co.call);
// TODO
assert(false, "UserFnCall is unimplemented");
return new P.VariablePattern(`_FN_${++this.lastAutoId}`, {
filter: this.trCall(co.call),
});
}

private trValue(value: Component): P.Pattern {
return new P.ConstPattern(new Name([value]));
}

private trCall(call: UserFnCall): P.VariablePattern.Filter {
this.neededFns.add(call.fn);
return new LvsFilter(this.vtable, call.fn, Array.from(call.args, (a) => {
if (a.value !== undefined) {
return a.value;
}
assert(a.tag !== undefined);
return this.nameTag(a.tag);
}));
}

private processRules(): void {
for (const node of this.model.nodes) {
const packetIds = this.namePattern(node);
Expand All @@ -126,3 +150,43 @@ class Translator {
}
}
}

class LvsFilter implements P.VariablePattern.Filter, printESM.PrintableFilter {
constructor(
vtable: Vtable,
private readonly fn: string,
private readonly binds: Array<string | Component>,
) {
this.func = vtable.get(fn);
}

private readonly func?: UserFn;

public accept(name: Name, vars: P.Vars): boolean {
let args: Array<Component | undefined>;
return !!this.func &&
(args = Array.from(this.binds,
(b) => typeof b === "string" ? vars.get(b)?.get(0) : b)
).every((a) => !!a) &&
this.func(name.at(0), args);
}

public printESM(indent: string): string {
const lines: string[] = [];
lines.push(`${indent}{`);
lines.push(`${indent} accept(name, vars) {`);
lines.push(`${indent} const args = [`);
for (const b of this.binds) {
if (typeof b === "string") {
lines.push(`${indent} vars.get(${JSON.stringify(b)})?.get(0),`);
} else {
lines.push(`${indent} Component.from(${JSON.stringify(b.toString())}),`);
}
}
lines.push(`${indent} ];`);
lines.push(`${indent} return args.every(a => !!a) && lvsUserFns.${this.fn}(name.at(0), args);`);
lines.push(`${indent} }`);
lines.push(`${indent}}`);
return lines.join("\n");
}
}
3 changes: 3 additions & 0 deletions pkg/lvs/test-fixture/lvstlv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ export const pyndn1 = loadLVSTLV("pyndn1.tlv");

/** python-ndn LVS model sample "compiler and checker demonstration". */
export const pyndn2 = loadLVSTLV("pyndn2.tlv");

/** python-ndn LVS model sample "user functions". */
export const pyndn3 = loadLVSTLV("pyndn3.tlv");
1 change: 1 addition & 0 deletions pkg/lvs/test-fixture/pyndn3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#rule: /a/b & { b: $fn("c", a) }
Binary file added pkg/lvs/test-fixture/pyndn3.tlv
Binary file not shown.
27 changes: 23 additions & 4 deletions pkg/lvs/tests/pyndn.t.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import "@ndn/packet/test-fixture/expect";

import { Name } from "@ndn/packet";
// import { printESM, versec } from "@ndn/trust-schema";
import { printESM } from "@ndn/trust-schema";
import { console } from "@ndn/util";
import { expect, test } from "vitest";
import { expect, test, vi } from "vitest";

import { toPolicy } from "..";
import { pyndn0, pyndn1, pyndn2 } from "../test-fixture/lvstlv";
import { toPolicy, type UserFn } from "..";
import { pyndn0, pyndn1, pyndn2, pyndn3 } from "../test-fixture/lvstlv";

test("pyndn0", () => {
const model = pyndn0();
Expand Down Expand Up @@ -37,3 +39,20 @@ test("pyndn2", () => {
const model = pyndn2();
console.log(model.toString());
});

test("pyndn3", () => {
const model = pyndn3();
console.log(model.toString());

const $fn = vi.fn<UserFn>();
const policy = toPolicy(model, { $fn });
console.log(printESM(policy));

// $fn.mockReturnValue(true);
// expect(policy.match(new Name("/x/y"))).toHaveLength(1);
// expect($fn).toHaveBeenCalledOnce();
// expect($fn.mock.calls[0]![0]).toEqualComponent("y");
// expect($fn.mock.calls[0]![1]).toHaveLength(2);
// expect($fn.mock.calls[0]![1][0]).toEqualComponent("c");
// expect($fn.mock.calls[0]![1][1]).toEqualComponent("x");
});
2 changes: 1 addition & 1 deletion pkg/segmented-object/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@ndn/naming-convention2": "workspace:*",
"@ndn/packet": "workspace:*",
"@ndn/util": "workspace:*",
"@zenfs/core": "1.1.1",
"@zenfs/core": "1.1.2",
"it-keepalive": "^1.2.0",
"mnemonist": "^0.39.8",
"obliterator": "^2.0.4",
Expand Down
8 changes: 3 additions & 5 deletions pkg/trust-schema/src/schema/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,9 @@ export class VariablePattern extends Pattern {
for (let i = this.minComps, max = Math.min(state.tailLength, this.maxComps); i <= max; ++i) {
const value = state.tail(i);
for (const innerVars of this.innerMatch(value)) {
if (this.filtersAccept(value, innerVars)) {
const s = state.extend(i, innerVars, [[this.id, value]]);
if (s) {
yield s;
}
const s = state.extend(i, innerVars, [[this.id, value]]);
if (s && this.filtersAccept(value, s.vars)) {
yield s;
}
}
}
Expand Down
18 changes: 15 additions & 3 deletions pkg/trust-schema/src/schema/print.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ function printPattern(p: Pattern, indent = ""): string {
opts.push(`inner: ${printPattern(p.inner, indent).trimStart()}`);
}
if (p.filter) {
opts.push(`filter: { accept() { throw new Error(${
JSON.stringify(`cannot translate filter ${p.filter.constructor.name}`)}) } }`);
const filter = p.filter as Partial<printESM.PrintableFilter>;
if (typeof filter.printESM === "function") {
opts.push(`filter: ${filter.printESM(indent).trimStart()}`);
} else {
opts.push(`filter: { accept(name, vars) { throw new Error(${
JSON.stringify(`cannot translate filter ${p.filter.constructor.name}`)}) } }`);
}
}
const optsArg = opts.length === 0 ? "" : `, { ${opts.join(", ")} }`;
return `${indent}new P.VariablePattern(${JSON.stringify(p.id)}${optsArg})`;
Expand Down Expand Up @@ -46,7 +51,8 @@ function printSequence(typ: string, list: Pattern[], indent: string): string {
export function printESM(policy: TrustSchemaPolicy): string {
const lines: string[] = [];
lines.push(
"import { pattern as P, TrustSchemaPolicy } from \"@ndn/trust-schema\";",
"import { TrustSchemaPolicy, pattern as P } from \"@ndn/trust-schema\";",
"import { Name, Component } from \"@ndn/packet\";",
"",
"export const policy = new TrustSchemaPolicy();",
"",
Expand All @@ -61,3 +67,9 @@ export function printESM(policy: TrustSchemaPolicy): string {
lines.push("");
return lines.join("\n");
}

export namespace printESM {
export interface PrintableFilter extends VariablePattern.Filter {
printESM: (indent: string) => string;
}
}

0 comments on commit 2d59f68

Please sign in to comment.