From 2d59f68c5ff031c8cc8c0571820f7a4f1d9d3fc7 Mon Sep 17 00:00:00 2001 From: Junxiao Shi Date: Fri, 25 Oct 2024 03:56:50 +0000 Subject: [PATCH] lvs: UserFn translate to ESM --- integ/browser-tests/package.json | 2 +- pkg/fileserver/package.json | 2 +- pkg/lvs/src/tlv.ts | 2 +- pkg/lvs/src/translate.ts | 84 ++++++++++++++++++++++--- pkg/lvs/test-fixture/lvstlv.ts | 3 + pkg/lvs/test-fixture/pyndn3.rs | 1 + pkg/lvs/test-fixture/pyndn3.tlv | Bin 0 -> 95 bytes pkg/lvs/tests/pyndn.t.ts | 27 ++++++-- pkg/segmented-object/package.json | 2 +- pkg/trust-schema/src/schema/pattern.ts | 8 +-- pkg/trust-schema/src/schema/print.ts | 18 +++++- 11 files changed, 123 insertions(+), 26 deletions(-) create mode 100644 pkg/lvs/test-fixture/pyndn3.rs create mode 100644 pkg/lvs/test-fixture/pyndn3.tlv diff --git a/integ/browser-tests/package.json b/integ/browser-tests/package.json index 9721627d..c14d4bdd 100644 --- a/integ/browser-tests/package.json +++ b/integ/browser-tests/package.json @@ -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", diff --git a/pkg/fileserver/package.json b/pkg/fileserver/package.json index 8b8c1185..dfcff069 100644 --- a/pkg/fileserver/package.json +++ b/pkg/fileserver/package.json @@ -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", diff --git a/pkg/lvs/src/tlv.ts b/pkg/lvs/src/tlv.ts index a6cbe406..a53f1c0a 100644 --- a/pkg/lvs/src/tlv.ts +++ b/pkg/lvs/src/tlv.ts @@ -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() {} buildUserFnCall.subclass = UserFnCall; diff --git a/pkg/lvs/src/translate.ts b/pkg/lvs/src/translate.ts index 51dbaf0f..a2b10197 100644 --- a/pkg/lvs/src/translate.ts +++ b/pkg/lvs/src/translate.ts @@ -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; +export type VtableInput = Vtable | Record; + 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(); private readonly wantedNodes = new Set(); + public readonly neededFns = new Set(); private lastAutoId = 0; - public translate(): TrustSchemaPolicy { + public translate(): void { this.gatherTagSymbols(); this.gatherNodes(); this.processPatterns(); this.processRules(); - return this.policy; } private gatherTagSymbols(): void { @@ -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); @@ -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, + ) { + this.func = vtable.get(fn); + } + + private readonly func?: UserFn; + + public accept(name: Name, vars: P.Vars): boolean { + let args: Array; + 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"); + } +} diff --git a/pkg/lvs/test-fixture/lvstlv.ts b/pkg/lvs/test-fixture/lvstlv.ts index 6a123c4e..54b68a17 100644 --- a/pkg/lvs/test-fixture/lvstlv.ts +++ b/pkg/lvs/test-fixture/lvstlv.ts @@ -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"); diff --git a/pkg/lvs/test-fixture/pyndn3.rs b/pkg/lvs/test-fixture/pyndn3.rs new file mode 100644 index 00000000..5f33f6af --- /dev/null +++ b/pkg/lvs/test-fixture/pyndn3.rs @@ -0,0 +1 @@ +#rule: /a/b & { b: $fn("c", a) } diff --git a/pkg/lvs/test-fixture/pyndn3.tlv b/pkg/lvs/test-fixture/pyndn3.tlv new file mode 100644 index 0000000000000000000000000000000000000000..38cc28858334f508a285ee24b925b8f028054a41 GIT binary patch literal 95 zcmYdfVPF(sP-SGuWMoR_2GYT7s*H@vjEu>uKsp@AmsMqCQf6dw7IhRh6jWzcNy{^4 nRb=L1Og3f)DdPo7hchy2vMLvq=A@>x0mU>K6G1eSCSwu+6s!zz literal 0 HcmV?d00001 diff --git a/pkg/lvs/tests/pyndn.t.ts b/pkg/lvs/tests/pyndn.t.ts index b8ad89ae..0ee356c6 100644 --- a/pkg/lvs/tests/pyndn.t.ts +++ b/pkg/lvs/tests/pyndn.t.ts @@ -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(); @@ -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(); + 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"); +}); diff --git a/pkg/segmented-object/package.json b/pkg/segmented-object/package.json index 30b5a87d..a74aec7e 100644 --- a/pkg/segmented-object/package.json +++ b/pkg/segmented-object/package.json @@ -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", diff --git a/pkg/trust-schema/src/schema/pattern.ts b/pkg/trust-schema/src/schema/pattern.ts index f9d01bea..952453ac 100644 --- a/pkg/trust-schema/src/schema/pattern.ts +++ b/pkg/trust-schema/src/schema/pattern.ts @@ -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; } } } diff --git a/pkg/trust-schema/src/schema/print.ts b/pkg/trust-schema/src/schema/print.ts index 395f1ba2..a71f2f84 100644 --- a/pkg/trust-schema/src/schema/print.ts +++ b/pkg/trust-schema/src/schema/print.ts @@ -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; + 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})`; @@ -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();", "", @@ -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; + } +}