Skip to content

Commit

Permalink
Add jsonr package
Browse files Browse the repository at this point in the history
  • Loading branch information
andykswong committed Feb 5, 2024
1 parent 841cb71 commit b7a6ce0
Show file tree
Hide file tree
Showing 53 changed files with 2,039 additions and 48 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Core:
|[`@mithic/commons`](./packages/commons)|[![npm](https://img.shields.io/npm/v/@mithic/commons?style=flat-square&logo=npm)](https://www.npmjs.com/package/@mithic/commons)|Common utilities|
|[`@mithic/cqrs`](./packages/cqrs)|[![npm](https://img.shields.io/npm/v/@mithic/cqrs?style=flat-square&logo=npm)](https://www.npmjs.com/package/@mithic/cqrs)|CQRS interface|
|[`@mithic/crdt`](./packages/crdt)|[![npm](https://img.shields.io/npm/v/@mithic/crdt?style=flat-square&logo=npm)](https://www.npmjs.com/package/@mithic/crdt)|Eventsourced CRDT library|
|[`@mithic/jsonr`](./packages/jsonr)|[![npm](https://img.shields.io/npm/v/@mithic/jsonr?style=flat-square&logo=npm)](https://www.npmjs.com/package/@mithic/jsonr)|JSON intermediate representation for sandboxed scripting|
|[`@mithic/messaging`](./packages/messaging)|[![npm](https://img.shields.io/npm/v/@mithic/messaging?style=flat-square&logo=npm)](https://www.npmjs.com/package/@mithic/messaging)|Messaging interface|

Plugins:
Expand Down
3 changes: 3 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ component_management:
- component_id: "@mithic/crdt"
paths:
- "packages/crdt"
- component_id: "@mithic/jsonr"
paths:
- "packages/jsonr"
- component_id: "@mithic/messaging"
paths:
- "packages/messaging"
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from '@mithic/commons';
export * from '@mithic/collections';
export * from '@mithic/cqrs';
export * from '@mithic/crdt';
export * from '@mithic/jsonr';
export * from '@mithic/messaging';
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from '@mithic/commons';
export * from '@mithic/collections';
export * from '@mithic/cqrs';
export * from '@mithic/crdt';
export * from '@mithic/jsonr';
export * from '@mithic/messaging';
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@mithic/commons": "^0.3",
"@mithic/cqrs": "^0.3",
"@mithic/crdt": "^0.3",
"@mithic/jsonr": "^0.3",
"@mithic/messaging": "^0.3"
},
"devDependencies": {
Expand All @@ -75,6 +76,7 @@
"workspaces": [
"./packages/commons",
"./packages/collections",
"./packages/jsonr",
"./packages/messaging",
"./packages/cqrs",
"./packages/crdt",
Expand Down
2 changes: 1 addition & 1 deletion packages/commons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"prebuild": "npm run lint",
"build": "npm run tsc && npm run babel",
"lint": "eslint src --ext .ts",
"babel": "babel src -d dist -x '.ts' --out-file-extension .js --root-mode upward",
"babel": "babel src -d dist -x '.ts' --root-mode upward",
"tsc": "tsc --project tsconfig.build.json",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"test:update": "npm run test -- -u",
Expand Down
68 changes: 38 additions & 30 deletions packages/commons/src/async/promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,38 @@ export type ReduceAsync = {
): MaybePromise<U>;
};

/** Reduces a {@link MaybePromise} of array using a maybe-async reducer function. */
export const reduceAsync = maybeAsync(reduce) as ReduceAsync;
function* emptyCoroutine() { }

class MaybeAsyncCorountine<R, V> {
private coroutine: Generator<MaybePromise<V>, MaybePromise<R>, V> =
emptyCoroutine() as Generator<MaybePromise<V>, MaybePromise<R>, V>;

public constructor() {
this.run = this.run.bind(this);
this.resume = this.resume.bind(this);
}

public start(coroutine: Generator<MaybePromise<V>, MaybePromise<R>, V>): MaybePromise<R> {
this.coroutine = coroutine;
return this.run();
}

private run(resolved?: V): MaybePromise<R> {
let result;
while (!(result = resolved === void 0 ? this.coroutine.next() : this.coroutine.next(resolved)).done) {
const value = result.value;
if (isThenable(value)) { return value.then(this.run, this.resume); }
resolved = value;
}
return result.value;
}

private resume(e: unknown): MaybePromise<R> {
const { done, value } = this.coroutine.throw(e);
if (done) { return value; }
return isThenable(value) ? value.then(this.run, this.resume) : this.run(value);
}
}

/**
* Wraps a {@link MaybePromise}-yielding coroutine (generator function) into a function that returns {@link MaybePromise}.
Expand All @@ -65,35 +95,13 @@ export const reduceAsync = maybeAsync(reduce) as ReduceAsync;
*/
export function maybeAsync<R = unknown, Args extends unknown[] = unknown[]>(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
coroutine: (...args: Args) => Generator<unknown, MaybePromise<R>, any>,
coroutineFn: (...args: Args) => Generator<unknown, MaybePromise<R>, any>,
thisArg?: unknown,
): (...args: Args) => MaybePromise<R> {
return (...args) => {
return new MaybeAsyncCorountine(coroutine.call(thisArg, ...args)).run();
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const coroutine = new MaybeAsyncCorountine<R, any>();
return (...args) => coroutine.start(coroutineFn.call(thisArg, ...args));
}

class MaybeAsyncCorountine<V, R> {
public constructor(
private readonly iter: Generator<MaybePromise<V>, MaybePromise<R>, V>,
) {
this.run = this.run.bind(this);
this.resume = this.resume.bind(this);
}

public run(resolved?: V): MaybePromise<R> {
let result;
while (!(result = resolved === void 0 ? this.iter.next() : this.iter.next(resolved)).done) {
const value = result.value;
if (isThenable(value)) { return value.then(this.run, this.resume); }
resolved = value;
}
return result.value;
}

public resume(e: unknown): MaybePromise<R> {
const { done, value } = this.iter.throw(e);
if (done) { return value; }
return isThenable(value) ? value.then(this.run, this.resume) : this.run(value);
}
}
/** Reduces a {@link MaybePromise} of array using a maybe-async reducer function. */
export const reduceAsync = maybeAsync(reduce) as ReduceAsync;
2 changes: 1 addition & 1 deletion packages/cqrs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"prebuild": "npm run lint",
"build": "npm run tsc && npm run babel",
"lint": "eslint src --ext .ts",
"babel": "babel src -d dist -x '.ts' --out-file-extension .js --root-mode upward",
"babel": "babel src -d dist -x '.ts' --root-mode upward",
"tsc": "tsc --project tsconfig.build.json",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"doc": "typedoc"
Expand Down
8 changes: 6 additions & 2 deletions packages/cqrs/src/__tests__/iterator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ describe(AsyncSubscriber.name, () => {
const controller = new AbortController();
const subscriber = new AsyncSubscriber(eventBus, controller);
const events = [1, 2, 3];
events.forEach((event) => eventBus.dispatch(event));
for (const event of events) {
await eventBus.dispatch(event);
}
const result = [];

let actualError;
Expand Down Expand Up @@ -125,7 +127,9 @@ describe(AsyncSubscriber.name, () => {
it('should ignore new values on fcfs mode if buffer is full', async () => {
const subscriber = new AsyncSubscriber(eventBus, { bufferSize: 1, fcfs: true });
const events = [1, 2, 3];
events.forEach((event) => eventBus.dispatch(event));
for (const event of events) {
await eventBus.dispatch(event);
}

for await (const event of subscriber) {
expect(event).toEqual(events[0]); // later events dropped
Expand Down
2 changes: 1 addition & 1 deletion packages/cqrs/src/iterator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class AsyncSubscriber<Message> implements AsyncIterableIterator<Message>,
return this.close();
}

private async push(value: Message) {
private push(value: Message) {
const resolve = this.pullQueue.shift();
if (resolve) {
resolve(this.running ? { value, done: false } : { value: void 0, done: true });
Expand Down
2 changes: 1 addition & 1 deletion packages/crdt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"prebuild": "npm run lint",
"build": "npm run tsc && npm run babel",
"lint": "eslint src --ext .ts",
"babel": "babel src -d dist -x '.ts' --out-file-extension .js --root-mode upward",
"babel": "babel src -d dist -x '.ts' --root-mode upward",
"tsc": "tsc --project tsconfig.build.json",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"doc": "typedoc"
Expand Down
21 changes: 21 additions & 0 deletions packages/jsonr/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Andy K. S. Wong

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
38 changes: 38 additions & 0 deletions packages/jsonr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# @mithic/jsonr

[![mithic](https://img.shields.io/badge/project-mithic-blueviolet.svg?style=flat-square&logo=github)](https://github.com/andykswong/mithic)
[![npm](https://img.shields.io/npm/v/@mithic/jsonr?style=flat-square&logo=npm)](https://www.npmjs.com/package/@mithic/jsonr)
[![docs](https://img.shields.io/badge/docs-typedoc-blue?style=flat-square&logo=typescript&logoColor=white)](http://andykswong.github.io/mithic)
[![license: MIT](https://img.shields.io/badge/License-MIT-red.svg?style=flat-square)](./LICENSE)
[![build](https://img.shields.io/github/actions/workflow/status/andykswong/mithic/build.yaml?style=flat-square)](https://github.com/andykswong/mithic/actions/workflows/build.yaml)

<br/>

> mithic JSON intermediate representation for sandboxed scripting
<br/>

## Install
```shell
npm install --save @mithic/jsonr
```

## Basic Usage
To run an expression:
1. Use a `Parser` (`JsonAstParser`) to parse JSON string into AST
1. Use an `Evaluator` (currently `Interpreter` only) to evaluate the AST against an `Env` (`DefaultEnv`) to get a result as JS object

#### Simplified JavaScript
```js
import { DefaultEnv, Interpreter, JsonAstParser, Stdlib } from '@mithic/jsonr';

const env = new DefaultEnv(null, Stdlib);
const parser = new JsonAstParser();
const evaluator = new Interpreter();

const ast = parser.parse('["+",3,5]');
const result = evaluator.eval(ast, env); // 8
```

## License
This repository and the code inside it is licensed under the MIT License. Read [LICENSE](./LICENSE) for more information.
1 change: 1 addition & 0 deletions packages/jsonr/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('../../jest.config.cjs');
47 changes: 47 additions & 0 deletions packages/jsonr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@mithic/jsonr",
"version": "0.3.0",
"description": "JSON intermediate representation for sandboxed scripting",
"type": "module",
"sideEffects": false,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": "./dist/index.js"
},
"files": [
"/dist"
],
"scripts": {
"prepublishOnly": "npm run clean && npm run build && npm test && npm run doc",
"clean": "rimraf coverage docs dist",
"prebuild": "npm run lint",
"build": "npm run tsc && npm run babel",
"lint": "eslint src --ext .ts",
"babel": "babel src -d dist -x '.ts,.tsx' --root-mode upward",
"tsc": "tsc --project tsconfig.build.json",
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
"test:update": "npm run test -- -u",
"prestart": "npm run build",
"doc": "typedoc"
},
"repository": {
"type": "git",
"url": "git+https://github.com/andykswong/mithic.git"
},
"keywords": [
"mithic",
"typescript"
],
"author": "Andy K.S. Wong <andykswong@outlook.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/andykswong/mithic/issues"
},
"homepage": "https://github.com/andykswong/mithic",
"dependencies": {
"@mithic/commons": "^0.3"
},
"devDependencies": {
}
}
37 changes: 37 additions & 0 deletions packages/jsonr/src/__tests__/__snapshots__/integ.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Integration tests async should catch rejected promise from readline 1`] = `
[
[
"you said:",
"<nope, too slow>",
],
]
`;

exports[`Integration tests async should print the correct results 1`] = `
[
[
"you said:",
"testing",
],
]
`;

exports[`Integration tests macro should print the correct results 1`] = `
[
[
2,
],
[
3,
],
[
[
"+",
1,
2,
],
],
]
`;
7 changes: 7 additions & 0 deletions packages/jsonr/src/__tests__/data/async.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
";",
["async", "echo", ["prompt"], ["try", ["await", ["readline", "prompt", 2000]], ["catch", "e", "'<nope, too slow>"]]],
["let", "input", ["await", ["echo", "'your input: "]]],
["println", "'you said:", "input"],
null
]
9 changes: 9 additions & 0 deletions packages/jsonr/src/__tests__/data/macro.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
";",
["fn", "x", ["n"], ["+", "n", 1]],
["macro", "y", ["a", "op", "b"], ["[]", "op", "a", "b"]],
["println", ["x", 1]],
["println", ["y", 1, "+", 2]],
["println", ["macroexpand", ["y", 1, "+", 2]]],
null
]
Loading

0 comments on commit b7a6ce0

Please sign in to comment.