-
Notifications
You must be signed in to change notification settings - Fork 16
base JS/TS class prepared for method hook ups #58
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
Open
jbolda
wants to merge
5
commits into
main
Choose a base branch
from
ts-base-api
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
754a17a
base JS/TS class prepared for method hook ups
jbolda b071f3f
add simple bench, wire up parse for better bench
jbolda ff45a89
change .raw reparses
jbolda 9a4c99e
refactor in hopefully more clear use cases
jbolda 6a32ee4
split into separate files
jbolda File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| # cooklang (TypeScript wrapper) | ||
|
|
||
| Lightweight TypeScript wrapper through WASM for the Rust-based Cooklang parser. | ||
|
|
||
| This folder provides a thin JS/TS convenience layer around the WASM parser based on `cooklang-rs`. The primary exported class in this module is `CooklangParser` which can be used either as an instance (hold a recipe and operate on it) or as a functional utility (pass a recipe string to each method). | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Instance Usage | ||
|
|
||
| This pattern holds a recipe on the parser instance in which all properties and methods then act upon. | ||
|
|
||
| ```ts | ||
| import { CooklangParser } from "@cooklang/parser"; | ||
|
|
||
| const fancyRecipe = "Write your @recipe here!"; | ||
|
|
||
| // create a parser instance with a raw recipe string | ||
| const recipe = new CooklangParser(fancyRecipe); | ||
|
|
||
| // read basic fields populated by the wrapper | ||
| console.log(recipe.metadata); // TODO sample response | ||
| console.log(recipe.ingredients); // TODO sample response | ||
| console.log(recipe.sections); // TODO sample response | ||
|
|
||
| // render methods return the original string in the minimal implementation | ||
| console.log(recipe.renderPrettyString()); // TODO sample response | ||
| console.log(recipe.renderHTML()); // TODO sample response | ||
| ``` | ||
|
|
||
| ### Functional Usage | ||
|
|
||
| This pattern passes a string directly and doesn't require keeping an instance around. | ||
|
|
||
| ```ts | ||
| import { CooklangParser } from "@cooklang/parser"; | ||
|
|
||
| const parser = new CooklangParser(); | ||
| const recipeString = "Write your @recipe here!"; | ||
|
|
||
| // functional helpers accept a recipe string and return rendered output | ||
| console.log(parser.renderPrettyString(recipeString)); // TODO sample response | ||
| console.log(parser.renderHTML(recipeString)); // TODO sample response | ||
|
|
||
| // `parse` returns a recipe class | ||
| const parsed = parser.parse(recipeString); | ||
| console.log(parsed.metadata); // TODO sample response | ||
| console.log(parsed.ingredients); // TODO sample response | ||
| console.log(parsed.sections); // TODO sample response | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1 @@ | ||
| import { version, Parser } from "./pkg/cooklang_wasm"; | ||
|
|
||
| export { version, Parser }; | ||
| export type { ScaledRecipeWithReport } from "./pkg/cooklang_wasm"; | ||
| export * from "./src/parser"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { CooklangParser, ScaledRecipeWithReport } from "./parser.js"; | ||
| import { CooklangRendererBase, recipe } from "./renderers.js"; | ||
|
|
||
| export class CooklangRecipe extends CooklangParser { | ||
| #parsed: ScaledRecipeWithReport | null = null; | ||
| metadata = {}; | ||
| ingredients = new Map(); | ||
| // TODO should we use something other than array here? | ||
| sections = []; | ||
| cookware = new Map(); | ||
| timers = []; | ||
| constructor(raw: string) { | ||
| super(); | ||
| // @ts-expect-error | ||
| this.use = void 0; // disable use method on instance | ||
| this.render = {} as Record<string, () => any>; | ||
|
|
||
| this.render.prettyString = () => | ||
| CooklangRendererBase["prettyString"](this).renderWithParsed( | ||
| this.#parsed! | ||
| ); | ||
| this.render.html = () => | ||
| CooklangRendererBase["html"](this).renderWithParsed(this.#parsed!); | ||
|
|
||
| this.raw = raw; | ||
| } | ||
|
|
||
| #setRecipe(rawParsed: ScaledRecipeWithReport) { | ||
| const constructed = recipe(rawParsed); | ||
| this.metadata = constructed.metadata; | ||
| this.ingredients = constructed.ingredients; | ||
| this.sections = constructed.sections; | ||
| this.cookware = constructed.cookware; | ||
| this.timers = constructed.timers; | ||
| } | ||
|
|
||
| set raw(raw: string) { | ||
| const parsed = this.parse(raw); | ||
| this.#parsed = parsed; | ||
| this.#setRecipe(parsed); | ||
| } | ||
|
|
||
| get raw() { | ||
| return this.raw; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export * from "./parser"; | ||
| export * from "./renderers"; | ||
| export * from "./full"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { | ||
| version, | ||
| Parser as RustParser, | ||
| type ScaledRecipeWithReport, | ||
| } from "../pkg/cooklang_wasm"; | ||
| import type { Renderer } from "./renderers"; | ||
|
|
||
| // for temporary backwards compatibility, let's export it with the old name | ||
| const Parser = RustParser; | ||
| export { version, Parser, type ScaledRecipeWithReport }; | ||
|
|
||
| export class CooklangParser { | ||
| static version: string = version(); | ||
| public extensionList: string[]; | ||
| #rust_parser: RustParser; | ||
| constructor() { | ||
| this.extensionList = [] as string[]; | ||
| this.#rust_parser = new RustParser(); | ||
| } | ||
|
|
||
| // TODO create issue to fill this in | ||
| set extensions(extensions: string[]) { | ||
| this.extensionList = extensions; | ||
| } | ||
|
|
||
| get extensions() { | ||
| if (!this.extensionList) throw new Error("TODO"); | ||
| return this.extensionList; | ||
| } | ||
|
|
||
| use(...renderers: Record<string, Renderer>[]) { | ||
| for (const rendererObject of renderers) { | ||
| for (const key in rendererObject) { | ||
| if ((this as any).render[key]) | ||
| throw new Error(`Renderer key ${key} already exists on parser`); | ||
| const renderer = rendererObject[key]; | ||
| if (typeof renderer !== "function") | ||
| throw new Error(`Renderer ${key} is not a function`); | ||
| const instance = renderer(this); | ||
| (this as any).render[key] = instance.render; | ||
| } | ||
| } | ||
| return this; | ||
| } | ||
|
|
||
| render(renderFunction: Renderer, recipeString: string) { | ||
| return renderFunction(this).render(recipeString); | ||
| } | ||
|
|
||
| parse(recipeString: string) { | ||
| return this.#rust_parser.parse(recipeString); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { CooklangParser } from "./parser.js"; | ||
| import { type ScaledRecipeWithReport } from "../pkg/cooklang_wasm"; | ||
|
|
||
| export type Renderer = (parser: CooklangParser) => { | ||
| render: (recipeString: string) => any; | ||
| }; | ||
|
|
||
| export const CooklangRendererBase = { | ||
| prettyString(parser: CooklangParser) { | ||
| return { | ||
| // TODO fix return with actual pretty string | ||
| render: (recipeString: string) => recipeString, | ||
| // only for class CooklangRecipe, not required on other external renderers | ||
| renderWithParsed: (parsed: ScaledRecipeWithReport) => | ||
| "eventually pretty string", | ||
| }; | ||
| }, | ||
| html(parser: CooklangParser) { | ||
| return { | ||
| // TODO fix return with actual html string | ||
| render: (recipeString: string) => recipeString, | ||
| // only for class CooklangRecipe, not required on other external renderers | ||
| renderWithParsed: (parsed: ScaledRecipeWithReport) => "eventually html", | ||
| }; | ||
| }, | ||
| debug(parser: CooklangParser) { | ||
| // TODO debug parse this then return | ||
| return { | ||
| render: (recipeString: string) => ({ | ||
| version: CooklangParser.version, | ||
| ast: recipeString, | ||
| events: recipeString, | ||
| }), | ||
| }; | ||
| }, | ||
| recipe(parser: CooklangParser) { | ||
| return { | ||
| render: (recipeString: string) => { | ||
| const parsed = parser.parse(recipeString); | ||
| return recipe(parsed); | ||
| }, | ||
| }; | ||
| }, | ||
| }; | ||
|
|
||
| export const recipe = (rawParsed: ScaledRecipeWithReport) => { | ||
| return { | ||
| ...rawParsed.recipe, | ||
| ingredients: new Map( | ||
| rawParsed.recipe.ingredients.map((recipe) => [recipe.name, recipe]) | ||
| ), | ||
| }; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { beforeAll, describe, expect, it } from "vitest"; | ||
| import { CooklangRecipe, recipe as recipeFn } from "../src"; | ||
|
|
||
| const recipeString = "Make your first recipe with an @ingredient!"; | ||
|
|
||
| it("returns version number", () => { | ||
| expect(CooklangRecipe.version).toBeDefined(); | ||
| }); | ||
|
|
||
| describe("parser instance", () => { | ||
| let recipe: any; | ||
| let directParse: any; | ||
| beforeAll(() => { | ||
| recipe = new CooklangRecipe(recipeString); | ||
| directParse = recipeFn(recipe.parse(recipeString)); | ||
| }); | ||
|
|
||
| it("returns pretty stringified recipe", () => { | ||
| expect(recipe.render.prettyString()).toEqual("eventually pretty string"); | ||
| }); | ||
|
|
||
| it("returns basic html recipe", () => { | ||
| expect(recipe.render.html()).toEqual("eventually html"); | ||
| }); | ||
|
|
||
| it("returns metadata list", () => { | ||
| expect(recipe.metadata).toEqual(directParse.metadata); | ||
| }); | ||
|
|
||
| it("returns ingredients list", () => { | ||
| expect(recipe.ingredients).toEqual(directParse.ingredients); | ||
| }); | ||
|
|
||
| it("returns sections list", () => { | ||
| expect(recipe.sections).toEqual(directParse.sections); | ||
| }); | ||
|
|
||
| it("returns cookware list", () => { | ||
| expect(recipe.cookware).toEqual(directParse.cookware); | ||
| }); | ||
|
|
||
| it("returns timers list", () => { | ||
| expect(recipe.timers).toEqual(directParse.timers); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { bench, describe } from "vitest"; | ||
| import { CooklangParser, CooklangRecipe } from "../src"; | ||
|
|
||
| const recipeString = "Make your first @recipe!"; | ||
| describe("parser", () => { | ||
| bench("instance", () => { | ||
| const recipe = new CooklangRecipe(recipeString); | ||
| }); | ||
|
|
||
| // init the parser outside of the bench which | ||
| // technically saves a few cycles on the actual | ||
| // bench result vs the instance bench, but this is | ||
| // effectively the use case where you init once | ||
| // and reuse that parser over and over | ||
| const parser = new CooklangParser(); | ||
| bench("functional", () => { | ||
| parser.parse(recipeString); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { CooklangParser, CooklangRendererBase } from "../src"; | ||
|
|
||
| const recipeString = "Make your first recipe with an @ingredient!"; | ||
|
|
||
| it("returns version number", () => { | ||
| expect(CooklangParser.version).toBeDefined(); | ||
| }); | ||
|
|
||
| describe("parser with functional render", () => { | ||
| const parser = new CooklangParser(); | ||
|
|
||
| it("returns full parse of recipe string", () => { | ||
| const parsedRecipe = parser.parse(recipeString); | ||
| expect(typeof parsedRecipe).toEqual("object"); | ||
| }); | ||
|
|
||
| it("returns pretty stringified recipe", () => { | ||
| expect( | ||
| parser.render(CooklangRendererBase.prettyString, recipeString) | ||
| ).toEqual("Make your first recipe with an @ingredient!"); | ||
| }); | ||
|
|
||
| it("returns html recipe", () => { | ||
| expect(parser.render(CooklangRendererBase.html, recipeString)).toEqual( | ||
| "Make your first recipe with an @ingredient!" | ||
| ); | ||
| }); | ||
| }); | ||
|
|
||
| describe("parser using renderer", () => { | ||
| const parser = new CooklangParser().use(CooklangRendererBase); | ||
|
|
||
| it("returns full parse of recipe string", () => { | ||
| const parsedRecipe = parser.parse(recipeString); | ||
| expect(typeof parsedRecipe).toEqual("object"); | ||
| }); | ||
|
|
||
| it("returns pretty stringified recipe", () => { | ||
| expect(parser.render.prettyString(recipeString)).toEqual( | ||
| "Make your first recipe with an @ingredient!" | ||
| ); | ||
| }); | ||
|
|
||
| it("returns html recipe", () => { | ||
| expect(parser.render.html(recipeString)).toEqual( | ||
| "Make your first recipe with an @ingredient!" | ||
| ); | ||
| }); | ||
| }); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to point out that this is a major optimization! I'm thinking the "right way" should be the way we guide users towards through the library design. That's why I used the
staticparser in #60.To say it another way: I don't see the benefit of "instance" parsing. What do you think?