diff --git a/pkg/core/src/parser.ts b/pkg/core/src/parser.ts index be38a49f..fb100634 100644 --- a/pkg/core/src/parser.ts +++ b/pkg/core/src/parser.ts @@ -1,16 +1,14 @@ import { allTokens, - Read, - Connect, - Save, - Send, - Pass, - Within, And, - To, - Into, Buzzword, + Connect, Identifier, + Into, + Output, + Pass, + Send, + To, } from '@slangroom/core/tokens'; import { CstParser, type IToken } from '@slangroom/deps/chevrotain'; @@ -20,19 +18,15 @@ const Parser = new (class extends CstParser { this.performSelfAnalysis(); } - statements = this.RULE('statements', () => { - this.AT_LEAST_ONE_SEP({ - SEP: And, - DEF: () => this.SUBRULE(this.#statement), - }); - }); - - #statement = this.RULE('statement', () => { + statement = this.RULE('statement', () => { this.OPTION1(() => this.SUBRULE(this.#connect)); this.MANY(() => { this.SUBRULE(this.#sendpass); }); - this.SUBRULE(this.#readsave); + this.SUBRULE(this.#buzzwords); + this.OPTION(() => { + this.SUBRULE(this.#into); + }); }); #connect = this.RULE('connect', () => { @@ -51,35 +45,16 @@ const Parser = new (class extends CstParser { ALT: () => this.CONSUME(Pass), }, ]); - this.OPTION(() => this.SUBRULE(this.#buzzwords)); + this.SUBRULE(this.#buzzwords); this.CONSUME(Identifier); this.CONSUME(And); }); - #readsave = this.RULE('readsave', () => { - this.OR([{ ALT: () => this.SUBRULE(this.#read) }, { ALT: () => this.SUBRULE(this.#save) }]); - }); - - #save = this.RULE('save', () => { - this.CONSUME(Save); - this.SUBRULE(this.#buzzwords); - }); - - #read = this.RULE('read', () => { - this.CONSUME(Read); - this.SUBRULE(this.#buzzwords); - this.OPTION(() => this.SUBRULE(this.#into)); - }); - #into = this.RULE('into', () => { + this.CONSUME(And); + this.CONSUME(Output); this.CONSUME(Into); this.CONSUME(Identifier); - this.OPTION(() => this.SUBRULE(this.#within)); - }); - - #within = this.RULE('within', () => { - this.CONSUME(Within); - this.CONSUME(Identifier); }); #buzzwords = this.RULE('buzzwords', () => { @@ -92,7 +67,7 @@ export const CstVisitor = Parser.getBaseCstVisitorConstructor(); export const parse = (tokens: IToken[]) => { Parser.input = tokens; - const res = Parser.statements(); + const res = Parser.statement(); console.log(Parser.errors) return res }; diff --git a/pkg/core/src/plugin.ts b/pkg/core/src/plugin.ts index 8a4eef82..207885cf 100644 --- a/pkg/core/src/plugin.ts +++ b/pkg/core/src/plugin.ts @@ -48,13 +48,10 @@ export const buildNormalizedPharse = (phrase: string) => phrase.toLowerCase().replace(' ', '_'); export class Plugin { + #func: (...args: Jsonable[]) => Jsonable; #params: string[]; #phrase: string; - constructor(phrase: string, params: string[]) { - this.#params = params - this.#phrase = buildNormalizedPharse(phrase) - } protected buildParams(bindings: Map, execParams: ExecParams): Jsonable[] { const args = this.#params.map((v: any) => { @@ -70,13 +67,9 @@ export class Plugin { getPhrase() { return this.#phrase } -} - -export class ReadPlugin extends Plugin { - #func: (...args: Jsonable[]) => Jsonable; - constructor(phrase: string, params: string[], func: (...args: Jsonable[]) => Jsonable) { - super(phrase, params); + this.#params = params + this.#phrase = buildNormalizedPharse(phrase) this.#func = func; } diff --git a/pkg/core/src/slangroom.ts b/pkg/core/src/slangroom.ts index dc131bf1..242c3b02 100644 --- a/pkg/core/src/slangroom.ts +++ b/pkg/core/src/slangroom.ts @@ -3,51 +3,37 @@ import { ZenroomParams, zencodeExec } from '@slangroom/shared'; import { Plugin, ExecParams, - ReadPlugin, buildNormalizedPharse, } from '@slangroom/core/plugin'; import { lex } from '@slangroom/core/lexer'; import { parse } from '@slangroom/core/parser'; import { visit, type Statement } from '@slangroom/core/visitor'; -import { ActionType } from './visitor.js'; -import { Jsonable } from '@slangroom/shared'; type Plugins = Plugin | Set | Array>; export class Slangroom { - #readPlugins = new Map(); + #plugins = new Map(); constructor(first: Plugins, ...rest: Plugins[]) { const recurse = (x: Plugins) => { if (Array.isArray(x) || x instanceof Set) x.forEach(recurse); else { - if (x instanceof ReadPlugin) this.#readPlugins.set(x.getPhrase(), x); - else throw new Error("Unknown object") + this.#plugins.set(x.getPhrase(), x); } }; [first, ...rest].forEach(recurse); } async executePlugin(p: Statement, params: ExecParams) { - if(p.action.kind == ActionType.Read) { - const normalizedBuzzwords = buildNormalizedPharse(p.action.buzzwords) - const plugin = this.#readPlugins.get(normalizedBuzzwords) - if(plugin) { - const result = plugin.execute(p.bindings, params) - if(p.action.into.length > 0) { - let val: Jsonable = {} - if(p.action.into.length > 1) { - val[p.action.into[1] || ""] = result - } else { - val = result - } - params.set(p.action.into[0] || "", val) - } else { - params.set(normalizedBuzzwords, result) - } - } else { - throw new Error("Unknown phrase") + const normalizedBuzzwords = buildNormalizedPharse(p.buzzwords) + const plugin = this.#plugins.get(normalizedBuzzwords) + if(plugin) { + const result = plugin.execute(p.bindings, params) + if(p.into) { + params.set(p.into, result) } + } else { + throw new Error("Unknown phrase") } } @@ -91,3 +77,4 @@ const astify = (line: string) => { const cst = parse(tokens); return visit(cst); }; + diff --git a/pkg/core/src/tokens.ts b/pkg/core/src/tokens.ts index 728174d2..b22be395 100644 --- a/pkg/core/src/tokens.ts +++ b/pkg/core/src/tokens.ts @@ -40,9 +40,9 @@ export const Into = createToken({ pattern: /into/i, }); -export const Within = createToken({ - name: 'Within', - pattern: /within/i, +export const Output = createToken({ + name: 'Output', + pattern: /output/i, }); export const Buzzword = createToken({ @@ -63,11 +63,9 @@ export const Whitespace = createToken({ export const allTokens = [ Whitespace, - Read, - Save, Connect, Into, - Within, + Output, And, To, Identifier, diff --git a/pkg/core/src/visitor.ts b/pkg/core/src/visitor.ts index 3c05ee48..0ca127d7 100644 --- a/pkg/core/src/visitor.ts +++ b/pkg/core/src/visitor.ts @@ -6,23 +6,11 @@ export enum ActionType { Save, } -export type Read = { - kind: ActionType.Read, - buzzwords: string, - into: string[], -} - -export type Save = { - kind: ActionType.Save, - buzzwords: string, -} - -export type Action = Read | Save - export type Statement = { connect?: string, bindings: Map, - action: Action + buzzwords: string, + into?: string, } export class StatementBindings { @@ -48,18 +36,18 @@ export const Visitor = new (class extends CstVisitor { super(); this.validateVisitor(); } - statements(ctx: any) { - return ctx.statement.map((v: any) => this.visit(v)) - } statement(ctx: any) { const node: Statement = { - action: this.visit(ctx.readsave), + buzzwords: this.visit(ctx.buzzwords), bindings: new Map( ctx.sendpass?.map((v: any) => this.visit(v))), } if(ctx.connect) { node.connect = this.visit(ctx.connect) } + if(ctx.into) { + node.into = this.visit(ctx.into) + } return node } connect(ctx: any) { @@ -67,36 +55,10 @@ export const Visitor = new (class extends CstVisitor { } sendpass(ctx: any) { const identifier = ctx.Identifier[0].image.slice(1,-1) - if(ctx.buzzwords) { - return [this.visit(ctx.buzzwords), identifier] - } - return [identifier, identifier] - } - readsave(ctx: any): Action { - return this.visit(ctx.read || ctx.save) - } - save(ctx: any): Save { - return { - kind: ActionType.Save, - buzzwords: this.visit(ctx.buzzwords) - } - } - read(ctx: any): Read { - return { - kind: ActionType.Read, - buzzwords: this.visit(ctx.buzzwords), - into: ctx.into ? this.visit(ctx.into) : [] - } + return [this.visit(ctx.buzzwords), identifier] } into(ctx: any) { - let path = [ctx.Identifier[0].image.slice(1,-1)] - if(ctx.within) { - path.concat(this.visit(ctx.within)) - } - return path - } - within(ctx: any) { - return [ctx.Identifier[0].image.slice(1,-1)] + return ctx.Identifier[0].image.slice(1,-1) } buzzwords(ctx: any) { return ctx.Buzzword.map((v: any) => v.image).join(' '); diff --git a/pkg/core/test/slangroom.ts b/pkg/core/test/slangroom.ts index 1dac8d2d..eae3e7f6 100644 --- a/pkg/core/test/slangroom.ts +++ b/pkg/core/test/slangroom.ts @@ -1,40 +1,40 @@ import test from 'ava'; -import { ReadPlugin } from '../src/plugin.js'; +import { Plugin } from '../src/plugin.js'; import { Slangroom } from '../src/slangroom.js'; test("Runs all unknown statements", async (t) => { let useR1 = false; let useR2 = false; let useR3 = false; - const r1 = new ReadPlugin("a", [], (...[]) => { + const r1 = new Plugin("a", [], (...[]) => { useR1 = true; return "foo" }) - const r2 = new ReadPlugin("b", ["a"], (...[output]) => { + const r2 = new Plugin("b", ["a"], (...[a]) => { useR2 = true - t.is(output, "foo") + t.is(a, "foo") return "bar" }) - const r3 = new ReadPlugin("c d", ["a"], (...[output]) => { + const r3 = new Plugin("c d", ["a"], (...[a]) => { useR3 = true - t.is(output, "bar") + t.is(a, "bar") return "foobar" }) const script = ` Rule caller restroom-mw -Given I read A +Given I A and output into 'a' Given I have a 'string' named 'a' Then print 'a' -Then I pass 'a' and read B -Then I pass a 'b' and read c D -Then I pass a 'b' and read C d into 'mimmo' +Then I pass a 'a' and B and output into 'b' +Then I pass a 'b' and c D +Then I pass a 'b' and C d and output into 'mimmo' ` - const slangroom = new Slangroom(r1, r2, r3); + const slangroom = new Slangroom(r1, [r2, r3]); const res = await slangroom.execute(script, {}) t.truthy(useR1, "r1 is not used") t.truthy(useR2, "r2 is not used") t.truthy(useR3, "r3 is not used") - t.deepEqual(res.result, { a: 'foo', b: 'bar', c_d: 'foobar', mimmo: 'foobar' }, res.logs) + t.deepEqual(res.result, { a: 'foo', b: 'bar', mimmo: 'foobar' }, res.logs) }) diff --git a/pkg/core/test/visitor.ts b/pkg/core/test/visitor.ts index 6ac40971..4120ec73 100644 --- a/pkg/core/test/visitor.ts +++ b/pkg/core/test/visitor.ts @@ -1,7 +1,7 @@ import test from 'ava'; import { lex } from '@slangroom/core/lexer'; import { parse } from '@slangroom/core/parser'; -import { ActionType, visit } from '@slangroom/core/visitor'; +import { visit } from '@slangroom/core/visitor'; const astify = (line: string) => { const { tokens } = lex(line); @@ -11,46 +11,39 @@ const astify = (line: string) => { test('generated ast is correct', async (t) => { const cases = { - 'read the ethereum balance': [{ - action: { - kind: ActionType.Read, - buzzwords: "the ethereum balance", - into: [] - }, + 'read the ethereum balance': { + buzzwords: "read the ethereum balance", bindings: new Map() - }], - "pass address 'addr' and send 'contract' and read the ethereum balance": [{ - action: { - kind: ActionType.Read, - buzzwords: "the ethereum balance", - into: [] - }, + }, + "pass address 'addr' and send contract 'contract' and read the ethereum balance": { + buzzwords: "read the ethereum balance", bindings: new Map([ ["address","addr"], ["contract","contract"], ]) - }], - "connect to 'foo' and read the ethereum balance": [{ + }, + "connect to 'foo' and read the ethereum balance": { connect: 'foo', - action: { - kind: ActionType.Read, - buzzwords: "the ethereum balance", - into: [] - }, + buzzwords: "read the ethereum balance", bindings: new Map() - }], - "connect to 'foo' and pass address 'addr' and send 'contract' and read the ethereum balance": [{ + }, + "connect to 'foo' and pass address 'addr' and send contract 'contract' and read the ethereum balance": { connect: 'foo', - action: { - kind: ActionType.Read, - buzzwords: "the ethereum balance", - into: [] - }, + buzzwords: "read the ethereum balance", bindings: new Map([ ["address","addr"], ["contract","contract"], ]) - }], + }, + "connect to 'foo' and pass address 'addr' and send contract 'contract' and read the ethereum balance and output into 'var'": { + connect: 'foo', + buzzwords: "read the ethereum balance", + bindings: new Map([ + ["address","addr"], + ["contract","contract"], + ]), + into: 'var', + }, }; for (const [line, astWant] of Object.entries(cases)) { const astHave = astify(line);