mirror of
https://github.com/zhigang1992/math-literal.git
synced 2026-01-12 16:32:51 +08:00
feat: first commit(by dts)
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text eol=lf
|
||||
32
.github/workflows/main.yml
vendored
Normal file
32
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: CI
|
||||
on: [push]
|
||||
jobs:
|
||||
build:
|
||||
name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }}
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
node: ['14.x', '16.x']
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node ${{ matrix.node }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: Install deps and build (with cache)
|
||||
uses: bahmutov/npm-install@v1
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
- name: Test
|
||||
run: yarn test --ci --coverage --maxWorkers=2
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
12
.github/workflows/size.yml
vendored
Normal file
12
.github/workflows/size.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
name: size
|
||||
on: [pull_request]
|
||||
jobs:
|
||||
size:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI_JOB_NUMBER: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: andresz1/size-limit-action@v1
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
node_modules
|
||||
dist
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Kyle Fang
|
||||
|
||||
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.
|
||||
103
README.md
Normal file
103
README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# DTS User Guide
|
||||
|
||||
Congrats! You just saved yourself hours of work by bootstrapping this project with DTS. Let’s get you oriented with what’s here and how to use it.
|
||||
|
||||
> This DTS setup is meant for developing libraries (not apps!) that can be published to NPM. If you’re looking to build a Node app, you could use `ts-node-dev`, plain `ts-node`, or simple `tsc`.
|
||||
|
||||
> If you’re new to TypeScript, checkout [this handy cheatsheet](https://devhints.io/typescript)
|
||||
|
||||
## Commands
|
||||
|
||||
DTS scaffolds your new library inside `/src`.
|
||||
|
||||
To run DTS, use:
|
||||
|
||||
```bash
|
||||
npm start # or yarn start
|
||||
```
|
||||
|
||||
This builds to `/dist` and runs the project in watch mode so any edits you save inside `src` causes a rebuild to `/dist`.
|
||||
|
||||
To do a one-off build, use `npm run build` or `yarn build`.
|
||||
|
||||
To run tests, use `npm test` or `yarn test`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Code quality is set up for you with `prettier`, `husky`, and `lint-staged`. Adjust the respective fields in `package.json` accordingly.
|
||||
|
||||
### Jest
|
||||
|
||||
Jest tests are set up to run with `npm test` or `yarn test`.
|
||||
|
||||
### Bundle Analysis
|
||||
|
||||
[`size-limit`](https://github.com/ai/size-limit) is set up to calculate the real cost of your library with `npm run size` and visualize the bundle with `npm run analyze`.
|
||||
|
||||
#### Setup Files
|
||||
|
||||
This is the folder structure we set up for you:
|
||||
|
||||
```txt
|
||||
/src
|
||||
index.ts # EDIT THIS
|
||||
/test
|
||||
index.test.ts # EDIT THIS
|
||||
.gitignore
|
||||
package.json
|
||||
README.md # EDIT THIS
|
||||
tsconfig.json
|
||||
```
|
||||
|
||||
### Rollup
|
||||
|
||||
DTS uses [Rollup](https://rollupjs.org) as a bundler and generates multiple rollup configs for various module formats and build settings. See [Optimizations](#optimizations) for details.
|
||||
|
||||
### TypeScript
|
||||
|
||||
`tsconfig.json` is set up to interpret `dom` and `esnext` types, as well as `react` for `jsx`. Adjust according to your needs.
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
Two actions are added by default:
|
||||
|
||||
- `main` which installs deps w/ cache, lints, tests, and builds on all pushes against a Node and OS matrix
|
||||
- `size` which comments cost comparison of your library on every pull request using [`size-limit`](https://github.com/ai/size-limit)
|
||||
|
||||
## Optimizations
|
||||
|
||||
Please see the main `dts` [optimizations docs](https://github.com/weiran-zsd/dts-cli#optimizations). In particular, know that you can take advantage of development-only optimizations:
|
||||
|
||||
```js
|
||||
// ./types/index.d.ts
|
||||
declare var __DEV__: boolean;
|
||||
|
||||
// inside your code...
|
||||
if (__DEV__) {
|
||||
console.log('foo');
|
||||
}
|
||||
```
|
||||
|
||||
You can also choose to install and use [invariant](https://github.com/weiran-zsd/dts-cli#invariant) and [warning](https://github.com/weiran-zsd/dts-cli#warning) functions.
|
||||
|
||||
## Module Formats
|
||||
|
||||
CJS, ESModules, and UMD module formats are supported.
|
||||
|
||||
The appropriate paths are configured in `package.json` and `dist/index.js` accordingly. Please report if any issues are found.
|
||||
|
||||
## Named Exports
|
||||
|
||||
Per Palmer Group guidelines, [always use named exports.](https://github.com/palmerhq/typescript#exports) Code split inside your React app instead of your React library.
|
||||
|
||||
## Including Styles
|
||||
|
||||
There are many ways to ship styles, including with CSS-in-JS. DTS has no opinion on this, configure how you like.
|
||||
|
||||
For vanilla CSS, you can include it at the root directory and add it to the `files` section in your `package.json`, so that it can be imported separately by your users and run through their bundler's loader.
|
||||
|
||||
## Publishing to NPM
|
||||
|
||||
We recommend using [np](https://github.com/sindresorhus/np).
|
||||
11410
package-lock.json
generated
Normal file
11410
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
62
package.json
Normal file
62
package.json
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "math-literal",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"author": "Kyle Fang",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/mathstring.esm.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"analyze": "size-limit --why",
|
||||
"build": "dts build",
|
||||
"lint": "dts lint",
|
||||
"prepare": "dts build",
|
||||
"size": "size-limit",
|
||||
"start": "dts watch",
|
||||
"test": "dts test"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "dts lint"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"size-limit": [
|
||||
{
|
||||
"path": "dist/mathstring.cjs.production.min.js",
|
||||
"limit": "10 KB"
|
||||
},
|
||||
{
|
||||
"path": "dist/mathstring.esm.js",
|
||||
"limit": "10 KB"
|
||||
}
|
||||
],
|
||||
"devDependencies": {
|
||||
"@size-limit/preset-small-lib": "^11.1.6",
|
||||
"@tsconfig/recommended": "^1.0.7",
|
||||
"@types/node": "^22.7.9",
|
||||
"dts-cli": "^2.0.5",
|
||||
"husky": "^9.1.6",
|
||||
"size-limit": "^11.1.6",
|
||||
"tslib": "^2.8.0",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"decimal.js": "^10.4.3"
|
||||
}
|
||||
}
|
||||
371
src/BigNumber.ts
Normal file
371
src/BigNumber.ts
Normal file
@@ -0,0 +1,371 @@
|
||||
import { Decimal as Big } from 'decimal.js';
|
||||
|
||||
type OneOrMore<T> = [T, ...T[]];
|
||||
|
||||
export type BigNumberSource =
|
||||
| number
|
||||
| string
|
||||
| bigint
|
||||
| Big.Value
|
||||
| Big
|
||||
| BigNumber;
|
||||
|
||||
const toBig = (num: BigNumberSource): Big => {
|
||||
if (num instanceof Big) {
|
||||
return num;
|
||||
}
|
||||
|
||||
if (BigNumber.isBigNumber(num)) {
|
||||
return num as any;
|
||||
}
|
||||
|
||||
if (typeof num === 'bigint') {
|
||||
return new Big(num.toString());
|
||||
}
|
||||
|
||||
return new Big(num as any);
|
||||
};
|
||||
|
||||
const fromBig = (num: Big): BigNumber => {
|
||||
return num as unknown as BigNumber;
|
||||
};
|
||||
|
||||
export type BigNumber = Omit<Big, keyof Big> & {
|
||||
readonly ___unique: unique symbol;
|
||||
___B: 'BigNumber';
|
||||
};
|
||||
export namespace BigNumber {
|
||||
export const {
|
||||
ROUND_UP: roundUp,
|
||||
ROUND_DOWN: roundDown,
|
||||
ROUND_HALF_UP: roundHalfUp,
|
||||
ROUND_HALF_DOWN: roundHalfDown,
|
||||
ROUND_HALF_EVEN: roundHalfEven,
|
||||
ROUND_FLOOR: roundFloor,
|
||||
ROUND_CEIL: roundCeil,
|
||||
ROUND_HALF_FLOOR: roundHalfFloor,
|
||||
ROUND_HALF_CEIL: roundHalfCeil,
|
||||
} = Big;
|
||||
export type RoundingMode =
|
||||
| typeof roundUp
|
||||
| typeof roundDown
|
||||
| typeof roundHalfUp
|
||||
| typeof roundHalfDown
|
||||
| typeof roundHalfEven
|
||||
| typeof roundFloor
|
||||
| typeof roundCeil
|
||||
| typeof roundHalfFloor
|
||||
| typeof roundHalfCeil;
|
||||
|
||||
export const isBigNumber = (num: any): num is BigNumber => {
|
||||
return num instanceof Big;
|
||||
};
|
||||
|
||||
export const safeFrom = (value: BigNumberSource): undefined | BigNumber => {
|
||||
try {
|
||||
return from(value);
|
||||
} catch (_) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const from = (value: BigNumberSource): BigNumber => {
|
||||
return fromBig(toBig(value as any));
|
||||
};
|
||||
|
||||
// biome-ignore lint/suspicious/noShadowRestrictedNames: override default toString
|
||||
export const toString = (value: BigNumberSource): string => {
|
||||
// const prevConf = Big.config({})
|
||||
// // https://github.com/MikeMcl/bignumber.js/blob/4120e6a3b9086a71658db5e3c0db68fd31c35354/bignumber.mjs#L67
|
||||
// Big.config({ EXPONENTIAL_AT: 1e9 })
|
||||
const res = toBig(value).toString();
|
||||
// Big.config(prevConf)
|
||||
return res;
|
||||
};
|
||||
|
||||
export const toNumber = (value: BigNumberSource): number => {
|
||||
return toBig(value).toNumber();
|
||||
};
|
||||
|
||||
export const toBigInt = (value: BigNumberSource): bigint => {
|
||||
return BigInt(toBig(value).toString());
|
||||
};
|
||||
|
||||
export const toFixed = curry2(
|
||||
(
|
||||
options: {
|
||||
precision?: number;
|
||||
roundingMode?: RoundingMode;
|
||||
},
|
||||
value: BigNumberSource,
|
||||
): string => {
|
||||
return toBig(value).toFixed(
|
||||
options.precision ?? 0,
|
||||
options.roundingMode ?? roundFloor,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const toExponential = curry2(
|
||||
(
|
||||
options: {
|
||||
precision?: number;
|
||||
roundingMode?: RoundingMode;
|
||||
},
|
||||
value: BigNumberSource,
|
||||
): string => {
|
||||
return toBig(value).toExponential(
|
||||
options.precision ?? 0,
|
||||
options.roundingMode ?? roundFloor,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const isNegative = (value: BigNumberSource): boolean => {
|
||||
return toBig(value).lt(0);
|
||||
};
|
||||
|
||||
export const isGtZero = (value: BigNumberSource): boolean => {
|
||||
return toBig(value).gt(0);
|
||||
};
|
||||
|
||||
export const isZero = (value: BigNumberSource): boolean => {
|
||||
return toBig(value).eq(0);
|
||||
};
|
||||
|
||||
export const isEq = curry2(
|
||||
(value: BigNumberSource, a: BigNumberSource): boolean => {
|
||||
return toBig(value).eq(toBig(a));
|
||||
},
|
||||
);
|
||||
|
||||
export const isGt = curry2(
|
||||
(value: BigNumberSource, a: BigNumberSource): boolean => {
|
||||
return toBig(value).gt(toBig(a));
|
||||
},
|
||||
);
|
||||
|
||||
export const isGte = curry2(
|
||||
(value: BigNumberSource, a: BigNumberSource): boolean => {
|
||||
return toBig(value).gte(toBig(a));
|
||||
},
|
||||
);
|
||||
|
||||
export const isLt = curry2(
|
||||
(value: BigNumberSource, a: BigNumberSource): boolean => {
|
||||
return toBig(value).lt(toBig(a));
|
||||
},
|
||||
);
|
||||
|
||||
export const isLte = curry2(
|
||||
(value: BigNumberSource, a: BigNumberSource): boolean => {
|
||||
return toBig(value).lte(toBig(a));
|
||||
},
|
||||
);
|
||||
|
||||
export const setPrecision = curry2(
|
||||
(
|
||||
options: {
|
||||
precision?: number;
|
||||
roundingMode?: RoundingMode;
|
||||
},
|
||||
value: BigNumberSource,
|
||||
): BigNumber => {
|
||||
return fromBig(
|
||||
toBig(
|
||||
toBig(value).toPrecision(
|
||||
options.precision ?? 0,
|
||||
options.roundingMode ?? roundFloor,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const getPrecision = (value: BigNumberSource): number => {
|
||||
return toBig(value).d?.length ?? 0 - ((toBig(value).e ?? -1) + 1);
|
||||
};
|
||||
|
||||
export const getIntegerLength = (value: BigNumberSource): number => {
|
||||
return (toBig(value).e ?? -1) + 1;
|
||||
};
|
||||
|
||||
export const leftMoveDecimals = curry2(
|
||||
(distance: number, value: BigNumberSource): BigNumber =>
|
||||
moveDecimals({ distance }, value),
|
||||
);
|
||||
|
||||
export const rightMoveDecimals = curry2(
|
||||
(distance: number, value: BigNumberSource): BigNumber =>
|
||||
moveDecimals({ distance: -distance }, value),
|
||||
);
|
||||
|
||||
export const moveDecimals = curry2(
|
||||
(options: { distance: number }, value: BigNumberSource): BigNumber => {
|
||||
if (options.distance > 0) {
|
||||
return fromBig(toBig(value).div(10 ** options.distance));
|
||||
}
|
||||
|
||||
if (options.distance < 0) {
|
||||
return fromBig(toBig(value).mul(10 ** -options.distance));
|
||||
}
|
||||
|
||||
// distance === 0
|
||||
return from(value);
|
||||
},
|
||||
);
|
||||
|
||||
export const getDecimalPart = curry2(
|
||||
(
|
||||
options: { precision: number },
|
||||
value: BigNumberSource,
|
||||
): undefined | string => {
|
||||
/**
|
||||
* `toString` will return `"1e-8"` in some case, so we choose `toFixed` here
|
||||
*/
|
||||
const formatted = toFixed(
|
||||
{
|
||||
precision: Math.min(getPrecision(value), options.precision),
|
||||
roundingMode: roundDown,
|
||||
},
|
||||
value,
|
||||
);
|
||||
|
||||
const [, decimals] = formatted.split('.');
|
||||
if (decimals == null) return undefined;
|
||||
return decimals;
|
||||
},
|
||||
);
|
||||
|
||||
export const abs = (value: BigNumberSource): BigNumber => {
|
||||
return fromBig(toBig(value).abs());
|
||||
};
|
||||
|
||||
export const ln = (value: BigNumberSource): BigNumber => {
|
||||
return fromBig(toBig(value).ln());
|
||||
};
|
||||
|
||||
export const exp = (value: BigNumberSource): BigNumber => {
|
||||
return fromBig(toBig(value).exp());
|
||||
};
|
||||
|
||||
export const neg = (value: BigNumberSource): BigNumber => {
|
||||
return fromBig(toBig(value).negated());
|
||||
};
|
||||
|
||||
export const sqrt = (value: BigNumberSource): BigNumber => {
|
||||
return fromBig(toBig(value).sqrt());
|
||||
};
|
||||
|
||||
export const add = curry2(
|
||||
(value: BigNumberSource, a: BigNumberSource): BigNumber => {
|
||||
return fromBig(toBig(value).plus(toBig(a)));
|
||||
},
|
||||
);
|
||||
|
||||
export const minus = curry2(
|
||||
(value: BigNumberSource, a: BigNumberSource): BigNumber => {
|
||||
return fromBig(toBig(value).minus(toBig(a)));
|
||||
},
|
||||
);
|
||||
|
||||
export const mul = curry2(
|
||||
(value: BigNumberSource, a: BigNumberSource): BigNumber => {
|
||||
return fromBig(toBig(value).mul(toBig(a)));
|
||||
},
|
||||
);
|
||||
|
||||
export const div = curry2(
|
||||
(value: BigNumberSource, a: BigNumberSource): BigNumber => {
|
||||
return fromBig(toBig(value).div(toBig(a)));
|
||||
},
|
||||
);
|
||||
|
||||
export const pow = curry2((value: BigNumberSource, a: number): BigNumber => {
|
||||
return fromBig(toBig(value).pow(a));
|
||||
});
|
||||
|
||||
export const round = curry2(
|
||||
(
|
||||
options: {
|
||||
precision?: number;
|
||||
roundingMode?: RoundingMode;
|
||||
},
|
||||
value: BigNumberSource,
|
||||
): BigNumber => {
|
||||
const n = toBig(value);
|
||||
return fromBig(
|
||||
n.toSD(
|
||||
Math.max(1, (n.e ?? 0) + 1 + (options.precision ?? 0)),
|
||||
options.roundingMode ?? roundFloor,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const toPrecision = curry2(
|
||||
(
|
||||
options: {
|
||||
precision?: number;
|
||||
roundingMode?: RoundingMode;
|
||||
},
|
||||
value: BigNumberSource,
|
||||
): string => {
|
||||
return toBig(value).toPrecision(
|
||||
options.precision ?? 0,
|
||||
options.roundingMode ?? roundFloor,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const ascend = curry2(
|
||||
(a: BigNumberSource, b: BigNumberSource): -1 | 0 | 1 =>
|
||||
isLt(a, b) ? -1 : isGt(a, b) ? 1 : 0,
|
||||
);
|
||||
|
||||
export const descend = curry2(
|
||||
(a: BigNumberSource, b: BigNumberSource): -1 | 0 | 1 =>
|
||||
isLt(a, b) ? 1 : isGt(a, b) ? -1 : 0,
|
||||
);
|
||||
|
||||
export const max = (numbers: OneOrMore<BigNumberSource>): BigNumber => {
|
||||
return fromBig(Big.max(...numbers.map(toBig)));
|
||||
};
|
||||
|
||||
export const min = (numbers: OneOrMore<BigNumberSource>): BigNumber => {
|
||||
return fromBig(Big.min(...numbers.map(toBig)));
|
||||
};
|
||||
|
||||
export const clamp = (
|
||||
range: [min: BigNumber, max: BigNumber],
|
||||
n: BigNumber,
|
||||
): BigNumber => {
|
||||
const [min, max] = range;
|
||||
if (isGte(n, max)) return max;
|
||||
if (isLte(n, min)) return min;
|
||||
return n;
|
||||
};
|
||||
|
||||
export const sum = (numbers: BigNumberSource[]): BigNumber => {
|
||||
return fromBig(Big.sum(...numbers.map(toBig)));
|
||||
};
|
||||
|
||||
export const ZERO = BigNumber.from(0);
|
||||
export const ONE = BigNumber.from(1e8);
|
||||
}
|
||||
|
||||
interface Curry2<X, Y, Ret> {
|
||||
(x: X): (y: Y) => Ret;
|
||||
(x: X, y: Y): Ret;
|
||||
}
|
||||
function curry2<X, Y, Ret>(fn: (x: X, y: Y) => Ret): Curry2<X, Y, Ret> {
|
||||
// @ts-ignore
|
||||
return (...args) => {
|
||||
if (args.length === 2) {
|
||||
// @ts-ignore
|
||||
return fn(...args);
|
||||
}
|
||||
// @ts-ignore
|
||||
return (y: Y) => fn(...args, y);
|
||||
};
|
||||
}
|
||||
52
src/ParsorC.ts
Normal file
52
src/ParsorC.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { ExpressionSource } from './bigNumberExpressionParser';
|
||||
|
||||
export class Parser<Source, T> {
|
||||
constructor(readonly parse: (input: Source[]) => [T, Source[]]) {}
|
||||
|
||||
// Array.from, Promise.resolve, RxJs.of, just, return
|
||||
static unit<Source, T>(value: T): Parser<Source, T> {
|
||||
return new Parser<Source, T>((i) => [value, i]);
|
||||
}
|
||||
|
||||
// then
|
||||
map<U>(f: (i: T) => U): Parser<Source, U> {
|
||||
return this.flatMap((t) => Parser.unit(f(t)));
|
||||
}
|
||||
|
||||
// SwitchMap, flatten, Ramda.chain, compactMap
|
||||
// async / await -> do notation
|
||||
// >>=
|
||||
flatMap<U>(f: (i: T) => Parser<Source, U>): Parser<Source, U> {
|
||||
return new Parser((input: Source[]) => {
|
||||
const [value, rest] = this.parse(input);
|
||||
return f(value).parse(rest);
|
||||
});
|
||||
}
|
||||
|
||||
// RxJs.combineLatest
|
||||
apply<U, V>(f: Parser<Source, U>, map: (t: T, u: U) => V): Parser<Source, V> {
|
||||
return this.flatMap((t) => f.map((u) => map(t, u)));
|
||||
}
|
||||
|
||||
or(other: Parser<Source, T>): Parser<Source, T> {
|
||||
return new Parser<Source, T>((i) => {
|
||||
try {
|
||||
return this.parse(i);
|
||||
} catch (e) {
|
||||
if (e instanceof ParserError) {
|
||||
return other.parse(i);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ParserError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
readonly remaining?: ExpressionSource[],
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
328
src/bigNumberExpressionParser.ts
Normal file
328
src/bigNumberExpressionParser.ts
Normal file
@@ -0,0 +1,328 @@
|
||||
import { BigNumber, type BigNumberSource } from './BigNumber';
|
||||
import { Parser, ParserError } from './ParsorC';
|
||||
|
||||
export type ExpressionSource =
|
||||
| {
|
||||
type: 'expression';
|
||||
expression: string;
|
||||
}
|
||||
| {
|
||||
type: 'value';
|
||||
};
|
||||
|
||||
const expression = <Op extends string>(
|
||||
input: Op,
|
||||
): Parser<ExpressionSource, Op> =>
|
||||
new Parser<ExpressionSource, Op>((i) => {
|
||||
const expressions = i.slice(0, input.length).map((a) => {
|
||||
if (a.type !== 'expression') {
|
||||
throw new ParserError(`not expression ${input}`);
|
||||
}
|
||||
return a.expression;
|
||||
});
|
||||
if (expressions.join('') === input) {
|
||||
return [input, i.slice(input.length)];
|
||||
}
|
||||
throw new ParserError(`not matching ${input}`, i);
|
||||
});
|
||||
|
||||
function fails<T>(): Parser<ExpressionSource, T> {
|
||||
return new Parser(() => {
|
||||
throw new ParserError('Fails');
|
||||
});
|
||||
}
|
||||
|
||||
function oneOf<T>(
|
||||
p: Parser<ExpressionSource, T>[],
|
||||
): Parser<ExpressionSource, T> {
|
||||
return p.reduce((a, b) => a.or(b), fails());
|
||||
}
|
||||
|
||||
// \d+
|
||||
function some<T>(
|
||||
p: Parser<ExpressionSource, T>,
|
||||
): Parser<ExpressionSource, T[]> {
|
||||
return p.flatMap((x) => someOrNone(p).map((xs) => [x, ...xs]));
|
||||
}
|
||||
// \d*
|
||||
function someOrNone<T>(
|
||||
p: Parser<ExpressionSource, T>,
|
||||
): Parser<ExpressionSource, T[]> {
|
||||
return some(p).or(Parser.unit([]));
|
||||
}
|
||||
|
||||
export type Execution = Parser<BigNumberSource, BigNumber>;
|
||||
|
||||
const number = new Parser<ExpressionSource, Execution>((i) => {
|
||||
const [head, ...tail] = i;
|
||||
if (head == null || head.type !== 'value') {
|
||||
throw new ParserError('Not a BigNumberSource');
|
||||
}
|
||||
const pickFirst: Execution = new Parser(([first, ...numbers]) => {
|
||||
if (first == null) {
|
||||
throw new ParserError('Not enough numbers');
|
||||
}
|
||||
return [BigNumber.from(first), numbers];
|
||||
});
|
||||
return [pickFirst, tail];
|
||||
});
|
||||
|
||||
const deferredExpression = defer(() => expr);
|
||||
|
||||
const paras = expression('(')
|
||||
.apply(deferredExpression, (_, b) => b)
|
||||
.apply(expression(')'), (a, _) => a);
|
||||
|
||||
const factor = number.or(paras);
|
||||
|
||||
const neg = expression('-').apply(factor, (_, b) => b.map(BigNumber.neg));
|
||||
const round = expression("round").apply(factor, (_, b) =>
|
||||
b.map(BigNumber.round({})),
|
||||
)
|
||||
const floor = expression("floor").apply(factor, (_, b) =>
|
||||
b.map(BigNumber.round({ roundingMode: BigNumber.roundDown })),
|
||||
)
|
||||
const ceil = expression("ceil").apply(factor, (_, b) =>
|
||||
b.map(BigNumber.round({ roundingMode: BigNumber.roundUp })),
|
||||
)
|
||||
const sqrt = expression('sqrt').apply(paras, (_, b) => b.map(BigNumber.sqrt));
|
||||
const abs = expression('abs').apply(paras, (_, b) => b.map(BigNumber.abs));
|
||||
const ln = expression('ln').apply(paras, (_, b) => b.map(BigNumber.ln));
|
||||
const exp = expression('exp').apply(paras, (_, b) => b.map(BigNumber.exp));
|
||||
|
||||
const minAndMax = oneOf([
|
||||
expression('max').map(
|
||||
() => (a: BigNumberSource) => (b: BigNumberSource) => BigNumber.max([a, b]),
|
||||
),
|
||||
expression('min').map(
|
||||
() => (a: BigNumberSource) => (b: BigNumberSource) => BigNumber.min([a, b]),
|
||||
),
|
||||
])
|
||||
.apply(expression('('), (a, _) => a)
|
||||
.apply(deferredExpression, (a, b) => b.map(a))
|
||||
.apply(expression(','), (a, _) => a)
|
||||
.apply(deferredExpression, (a, b) => a.apply(b, (x, y) => x(y)))
|
||||
.apply(expression(')'), (a, _) => a);
|
||||
|
||||
const numberWithDec = neg
|
||||
.or(round)
|
||||
.or(floor)
|
||||
.or(ceil)
|
||||
.or(sqrt)
|
||||
.or(abs)
|
||||
.or(ln)
|
||||
.or(exp)
|
||||
.or(minAndMax)
|
||||
.or(factor);
|
||||
|
||||
const term = numberWithDec.apply(
|
||||
someOrNone(
|
||||
oneOf(
|
||||
[
|
||||
expression('<<').map(
|
||||
() => (x: BigNumberSource) => (y: BigNumberSource) =>
|
||||
BigNumber.leftMoveDecimals(
|
||||
BigNumber.toNumber(y),
|
||||
x,
|
||||
) as BigNumberSource,
|
||||
),
|
||||
expression('>>').map(
|
||||
() => (x: BigNumberSource) => (y: BigNumberSource) =>
|
||||
BigNumber.rightMoveDecimals(
|
||||
BigNumber.toNumber(y),
|
||||
x,
|
||||
) as BigNumberSource,
|
||||
),
|
||||
oneOf([expression('**'), expression('^')]).map(
|
||||
() => (x: BigNumberSource) => (y: BigNumberSource) =>
|
||||
BigNumber.pow(x, BigNumber.toNumber(y)) as BigNumberSource,
|
||||
),
|
||||
expression('*~').map(
|
||||
() => (x: BigNumberSource) => (y: BigNumberSource) =>
|
||||
BigNumber.div(
|
||||
BigNumber.mul(x, y),
|
||||
BigNumber.ONE,
|
||||
) as BigNumberSource,
|
||||
),
|
||||
expression('/~').map(
|
||||
() => (x: BigNumberSource) => (y: BigNumberSource) =>
|
||||
BigNumber.mul(
|
||||
BigNumber.div(x, y),
|
||||
BigNumber.ONE,
|
||||
) as BigNumberSource,
|
||||
),
|
||||
expression('*' as string)
|
||||
.or(expression('x'))
|
||||
.map(() => BigNumber.mul),
|
||||
expression('/').map(() => BigNumber.div),
|
||||
].map((o) => o.apply(numberWithDec, (op, right) => [op, right] as const)),
|
||||
),
|
||||
),
|
||||
(left, list) =>
|
||||
list.reduce(
|
||||
(curr, [op, right]): Execution =>
|
||||
curr.apply(right, (x, y) => BigNumber.from(op(x)(y))),
|
||||
left,
|
||||
) as Execution,
|
||||
);
|
||||
|
||||
const expr: Parser<ExpressionSource, Execution> = term.apply(
|
||||
someOrNone(
|
||||
oneOf(
|
||||
[
|
||||
expression('+').map(() => BigNumber.add),
|
||||
expression('-').map(() => BigNumber.minus),
|
||||
].map((o) => o.apply(term, (op, right) => [op, right] as const)),
|
||||
),
|
||||
),
|
||||
(left, list) =>
|
||||
list.reduce(
|
||||
(curr, [op, right]): Execution =>
|
||||
curr.apply(right, (x, y) => BigNumber.from(op(x)(y))),
|
||||
left,
|
||||
) as Execution,
|
||||
);
|
||||
|
||||
function defer<T>(
|
||||
p: () => Parser<ExpressionSource, T>,
|
||||
): Parser<ExpressionSource, T> {
|
||||
return new Parser((input) => {
|
||||
return p().parse(input);
|
||||
});
|
||||
}
|
||||
|
||||
export const EOF = new Parser<ExpressionSource, null>((input) => {
|
||||
if (input.length === 0) {
|
||||
return [null, []];
|
||||
}
|
||||
throw new ParserError(
|
||||
`Did not reach parse to end, remaining "${input
|
||||
.map((x) => (x.type === 'expression' ? x.expression : '[value]'))
|
||||
.join(' ')}"`,
|
||||
);
|
||||
});
|
||||
|
||||
function extractExpressions(
|
||||
operators: TemplateStringsArray,
|
||||
args: BigNumberSource[],
|
||||
): { key: string; expressions: ExpressionSource[]; values: BigNumberSource[] } {
|
||||
const expressions: ExpressionSource[] = [];
|
||||
const values: BigNumberSource[] = [];
|
||||
for (let i = 0; i < operators.length; i++) {
|
||||
const chars = operators[i]!.split('').filter((x) => x.trim());
|
||||
for (const char of chars) {
|
||||
if (!isNaN(char as any)) {
|
||||
throw new ParserError(
|
||||
`You need to wrap all the numbers in \${}, found a ${char}`,
|
||||
);
|
||||
}
|
||||
expressions.push({
|
||||
type: 'expression',
|
||||
expression: char,
|
||||
});
|
||||
}
|
||||
const arg = args[i];
|
||||
if (arg != null) {
|
||||
if (
|
||||
typeof arg === 'string' &&
|
||||
['+', '-', '*', '/', '>', '<', '===', '==', '<=', '>=', '<<'].includes(
|
||||
arg.trim(),
|
||||
)
|
||||
) {
|
||||
expressions.push(
|
||||
...arg.split('').map((a) => ({
|
||||
type: 'expression' as const,
|
||||
expression: a,
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
expressions.push({
|
||||
type: 'value',
|
||||
});
|
||||
values.push(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
const key = expressions
|
||||
.map((x) => (x.type === 'expression' ? x.expression : '$0'))
|
||||
.join('');
|
||||
return { key, expressions, values };
|
||||
}
|
||||
|
||||
/**
|
||||
* this is the helper method to do BigNumber calculations
|
||||
* It supports normal operators like +, -, *, /, **
|
||||
* And also
|
||||
* - *leftMoveDecimals* via <<
|
||||
* - *mulDown* via *~
|
||||
* - *divDown* via /~
|
||||
*/
|
||||
export function math(
|
||||
operators: TemplateStringsArray,
|
||||
...args: Array<BigNumberSource>
|
||||
): BigNumber {
|
||||
const { expressions, values, key } = extractExpressions(operators, args);
|
||||
const cached = math.executionCache[key];
|
||||
if (cached != null) {
|
||||
return cached.parse(values)[0];
|
||||
}
|
||||
const [execution] = expr.apply(EOF, (a) => a).parse(expressions);
|
||||
math.executionCache[key] = execution;
|
||||
return execution.parse(values)[0];
|
||||
}
|
||||
math.executionCache = {} as { [key: string]: Execution };
|
||||
|
||||
const comparator = expr
|
||||
.apply(
|
||||
oneOf([
|
||||
expression('>=').map(() => BigNumber.isGte),
|
||||
expression('>').map(() => BigNumber.isGt),
|
||||
expression('<=').map(() => BigNumber.isLte),
|
||||
expression('<').map(() => BigNumber.isLt),
|
||||
expression<'===' | '=='>('===')
|
||||
.or(expression('=='))
|
||||
.map(() => BigNumber.isEq),
|
||||
expression<'!==' | '!='>('!==')
|
||||
.or(expression('!='))
|
||||
.map(
|
||||
() => (a: BigNumberSource) => (b: BigNumberSource) =>
|
||||
!BigNumber.isEq(a)(b),
|
||||
),
|
||||
]),
|
||||
(a, b) => [a, b] as const,
|
||||
)
|
||||
.apply(expr, ([a, op], b) => a.apply(b, (x, y) => op(x)(y)));
|
||||
|
||||
const logicOperator = comparator.apply(
|
||||
someOrNone(
|
||||
oneOf(
|
||||
[
|
||||
expression('&&').map(() => (a: boolean) => (b: boolean) => a && b),
|
||||
expression('||').map(() => (a: boolean) => (b: boolean) => a || b),
|
||||
].map((o) => o.apply(comparator, (op, right) => [op, right] as const)),
|
||||
),
|
||||
),
|
||||
(left, list) =>
|
||||
list.reduce(
|
||||
(curr, [op, right]) => curr.apply(right, (x, y) => op(x)(y)),
|
||||
left,
|
||||
),
|
||||
);
|
||||
|
||||
export function mathIs(
|
||||
operators: TemplateStringsArray,
|
||||
...args: Array<BigNumberSource>
|
||||
): boolean {
|
||||
const { key, expressions, values } = extractExpressions(operators, args);
|
||||
const cached = mathIs.executionCache[key];
|
||||
if (cached != null) {
|
||||
return cached.parse(values)[0];
|
||||
}
|
||||
const [execution] = logicOperator.apply(EOF, (a) => a).parse(expressions);
|
||||
mathIs.executionCache[key] = execution;
|
||||
return execution.parse(values)[0];
|
||||
}
|
||||
|
||||
mathIs.executionCache = {} as {
|
||||
[key: string]: Parser<BigNumberSource, boolean>;
|
||||
};
|
||||
2
src/index.ts
Normal file
2
src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './BigNumber';
|
||||
export * from './bigNumberExpressionParser';
|
||||
7
test/index.test.ts
Normal file
7
test/index.test.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { sum } from '../src/index';
|
||||
|
||||
describe('sum', () => {
|
||||
it('adds two numbers together', () => {
|
||||
expect(sum(1, 1)).toEqual(2);
|
||||
});
|
||||
});
|
||||
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@tsconfig/recommended/tsconfig.json",
|
||||
"include": ["src", "types"],
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"moduleResolution": "Node",
|
||||
"allowImportingTsExtensions": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user