Compare commits
10 Commits
9f463b4774
...
2cca20ae00
| Author | SHA1 | Date | |
|---|---|---|---|
| 2cca20ae00 | |||
| 7e76fc4008 | |||
|
|
49b05d0876 | ||
|
|
9c56b4ddb5 | ||
|
|
9b49d8c418 | ||
|
|
f918f3d5e0 | ||
|
|
51774f9b38 | ||
|
|
83c7d78fb2 | ||
|
|
eccfad5321 | ||
|
|
6a64d20b7e |
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@ -2,7 +2,7 @@ name: Deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
|
||||
4
.github/workflows/preview.yml
vendored
4
.github/workflows/preview.yml
vendored
@ -1,8 +1,8 @@
|
||||
name: Deploy
|
||||
name: Preview
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- master
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
|
||||
610
Cargo.lock
generated
610
Cargo.lock
generated
@ -2,6 +2,12 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
@ -17,6 +23,12 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bnum"
|
||||
version = "0.12.1"
|
||||
@ -29,6 +41,16 @@ version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@ -52,22 +74,190 @@ dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"fastnum",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"unicode-segmentation",
|
||||
"ureq",
|
||||
"wasm-bindgen",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastnum"
|
||||
version = "0.2.8"
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86cde0c9334bfed5ced962bd7acc266e02e254d71494787e4255d8ec4f7296d4"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastnum"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b875e26379edd7866a74cec720c6ae7bea3107aaf9fd3b14d7449d87d4c1986b"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bnum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"potential_utf",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_locale_core"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"litemap",
|
||||
"tinystr",
|
||||
"writeable",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_normalizer_data",
|
||||
"icu_properties",
|
||||
"icu_provider",
|
||||
"smallvec",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_normalizer_data"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
|
||||
dependencies = [
|
||||
"icu_collections",
|
||||
"icu_locale_core",
|
||||
"icu_properties_data",
|
||||
"icu_provider",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_properties_data"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
|
||||
|
||||
[[package]]
|
||||
name = "icu_provider"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"icu_locale_core",
|
||||
"writeable",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerotrie",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec",
|
||||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna_adapter"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
|
||||
dependencies = [
|
||||
"icu_normalizer",
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
@ -78,6 +268,18 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.178"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
@ -90,12 +292,37 @@ version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
|
||||
dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
@ -143,12 +370,140 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c"
|
||||
dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.145"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.101"
|
||||
@ -160,6 +515,27 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
@ -172,6 +548,54 @@ version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
@ -239,3 +663,183 @@ dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
|
||||
dependencies = [
|
||||
"webpki-roots 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
"yoke-derive",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke-derive"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
|
||||
dependencies = [
|
||||
"zerofrom-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom-derive"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
|
||||
dependencies = [
|
||||
"displaydoc",
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
|
||||
dependencies = [
|
||||
"yoke",
|
||||
"zerofrom",
|
||||
"zerovec-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerovec-derive"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
11
Cargo.toml
11
Cargo.toml
@ -10,6 +10,7 @@ homepage = "https://github.com/probablykasper/cpc#readme"
|
||||
repository = "https://github.com/probablykasper/cpc"
|
||||
documentation = "https://docs.rs/cpc"
|
||||
keywords = ["math", "expression", "evaluate", "units", "convert"]
|
||||
autobins = false
|
||||
categories = [
|
||||
"mathematics",
|
||||
"science",
|
||||
@ -18,6 +19,10 @@ categories = [
|
||||
"value-formatting",
|
||||
]
|
||||
|
||||
[[bin]]
|
||||
name = "cpc_cli"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
@ -25,6 +30,12 @@ crate-type = ["cdylib", "rlib"]
|
||||
fastnum = "0.2"
|
||||
unicode-segmentation = "1.12"
|
||||
web-time = "1.1.0"
|
||||
once_cell = "1.20"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
ureq = { version = "2.12", features = ["json"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
console_error_panic_hook = "0.1.7"
|
||||
|
||||
145
src/currency.rs
Normal file
145
src/currency.rs
Normal file
@ -0,0 +1,145 @@
|
||||
use crate::extra_units;
|
||||
use crate::units::UnitType;
|
||||
use fastnum::decimal::Context;
|
||||
use fastnum::D128;
|
||||
use serde_json::Value;
|
||||
|
||||
const EXCHANGE_RATE_API_USD_LATEST: &str = "https://api.exchangerate-api.com/v4/latest/USD";
|
||||
|
||||
fn parse_d128_from_json_number(v: &Value) -> Result<D128, String> {
|
||||
let s = match v {
|
||||
Value::Number(n) => n.to_string(),
|
||||
_ => return Err("Expected JSON number".to_string()),
|
||||
};
|
||||
D128::from_str(&s, Context::default())
|
||||
.map_err(|_| format!("Invalid decimal number: {s}"))
|
||||
}
|
||||
|
||||
/// Registers/updates currencies from the ExchangeRate API JSON response (base USD).
|
||||
///
|
||||
/// The JSON is expected to look like `{"base":"USD","rates":{"EUR":0.852,...}}`.
|
||||
///
|
||||
/// Internally we store a "weight" as USD-per-unit, so conversions use the existing
|
||||
/// `from.weight()/to.weight()` logic.
|
||||
pub fn register_currencies_from_exchange_rate_api_json(json: &str) -> Result<usize, String> {
|
||||
let v: Value = serde_json::from_str(json).map_err(|e| format!("Invalid JSON: {e}"))?;
|
||||
|
||||
let base = v
|
||||
.get("base")
|
||||
.and_then(|b| b.as_str())
|
||||
.ok_or("Missing 'base'")?;
|
||||
if base != "USD" {
|
||||
return Err(format!("Expected base USD but got {base}"));
|
||||
}
|
||||
|
||||
let rates = v
|
||||
.get("rates")
|
||||
.and_then(|r| r.as_object())
|
||||
.ok_or("Missing 'rates' object")?;
|
||||
|
||||
let mut count = 0usize;
|
||||
for (code, rate_value) in rates {
|
||||
// Skip weird/empty keys defensively
|
||||
if code.len() != 3 {
|
||||
continue;
|
||||
}
|
||||
let code_upper = code.to_ascii_uppercase();
|
||||
let code_lower = code.to_ascii_lowercase();
|
||||
|
||||
let rate = parse_d128_from_json_number(rate_value)?;
|
||||
if rate.is_zero() {
|
||||
continue;
|
||||
}
|
||||
// weight = USD per 1 {CODE} = 1 / (CODE per 1 USD)
|
||||
let weight = D128::ONE / rate;
|
||||
|
||||
extra_units::upsert_unit(
|
||||
&code_lower,
|
||||
&code_upper,
|
||||
&code_upper,
|
||||
UnitType::Currency,
|
||||
weight,
|
||||
);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn cache_path() -> std::path::PathBuf {
|
||||
let mut p = std::env::temp_dir();
|
||||
p.push("cpc_currency_rates_usd.json");
|
||||
p
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn init_currencies_blocking() -> Result<usize, String> {
|
||||
// 1) Load cached (if any) so currencies work offline.
|
||||
let mut cached_count = 0usize;
|
||||
let cache_path = cache_path();
|
||||
if let Ok(s) = std::fs::read_to_string(&cache_path) {
|
||||
if let Ok(n) = register_currencies_from_exchange_rate_api_json(&s) {
|
||||
cached_count = n;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Try refresh from network.
|
||||
let resp = ureq::get(EXCHANGE_RATE_API_USD_LATEST)
|
||||
.call()
|
||||
.map_err(|e| format!("Currency fetch failed: {e}"))?;
|
||||
let body = resp
|
||||
.into_string()
|
||||
.map_err(|e| format!("Currency fetch read failed: {e}"))?;
|
||||
|
||||
let n = register_currencies_from_exchange_rate_api_json(&body)?;
|
||||
// Best-effort cache write
|
||||
let _ = std::fs::write(&cache_path, body);
|
||||
|
||||
Ok(std::cmp::max(cached_count, n))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::extra_units;
|
||||
use crate::units::{convert, Unit};
|
||||
use crate::Number;
|
||||
use fastnum::dec128 as d;
|
||||
|
||||
#[test]
|
||||
fn register_and_convert_currency() {
|
||||
extra_units::clear_for_tests();
|
||||
|
||||
let json = r#"{
|
||||
"base": "USD",
|
||||
"rates": {
|
||||
"USD": 1,
|
||||
"EUR": 0.5
|
||||
}
|
||||
}"#;
|
||||
|
||||
let n = register_currencies_from_exchange_rate_api_json(json).unwrap();
|
||||
assert_eq!(n, 2);
|
||||
|
||||
let usd = extra_units::lookup_unit("usd").unwrap();
|
||||
let eur = extra_units::lookup_unit("eur").unwrap();
|
||||
|
||||
let one_usd = Number::new(d!(1), usd);
|
||||
let eur_amount = convert(one_usd, eur).unwrap();
|
||||
assert_eq!(eur_amount.value, d!(0.5));
|
||||
|
||||
let one_eur = Number::new(d!(1), eur);
|
||||
let usd_amount = convert(one_eur, usd).unwrap();
|
||||
assert_eq!(usd_amount.value, d!(2));
|
||||
|
||||
// Display name should be currency code
|
||||
assert_eq!(eur_amount.unit.singular(), "EUR");
|
||||
assert_eq!(eur_amount.unit.plural(), "EUR");
|
||||
|
||||
// Sanity: it's still a Unit
|
||||
let _ = Unit::NoUnit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -279,6 +279,9 @@ fn evaluate_node(ast_node: &AstNode) -> Result<Number, String> {
|
||||
mod tests {
|
||||
use crate::eval;
|
||||
use super::*;
|
||||
use crate::currency;
|
||||
use crate::extra_units;
|
||||
use crate::units::UnitType;
|
||||
|
||||
fn eval_default<'a>(input: &'a str) -> Number {
|
||||
let result = eval(input, true, false).unwrap();
|
||||
@ -299,6 +302,8 @@ mod tests {
|
||||
assert_eq!(eval_default("-1km to m"), Number::new(d!(-1000), Unit::Meter));
|
||||
assert_eq!(eval_num("2*-3*0.5"), "-3");
|
||||
assert_eq!(eval_num("-3^2"), "-9");
|
||||
assert_eq!(eval_num("e^2"), "7.3890560989306502272304274605750078132");
|
||||
assert_eq!(eval_num("e^2.5"), "12.1824939607034734380701759511679661832");
|
||||
assert_eq!(eval_num("-1+2"), "1");
|
||||
}
|
||||
|
||||
@ -334,4 +339,57 @@ mod tests {
|
||||
assert_eq!(eval_num("sin(2)"), "0.9092974268256816953960198659117448427");
|
||||
assert_eq!(eval_num("sin(-2)"), "-0.9092974268256816953960198659117448427");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_divide_currency_and_mass_by_volume() {
|
||||
// Ensure a clean registry so the IDs are deterministic
|
||||
extra_units::clear_for_tests();
|
||||
|
||||
// Minimal rates (USD base). 1 USD = 7.07 CNY
|
||||
let json = r#"{"base":"USD","rates":{"USD":1,"CNY":7.07}}"#;
|
||||
currency::register_currencies_from_exchange_rate_api_json(json).unwrap();
|
||||
|
||||
let x = eval_default("5.55 usd / 64 fl oz");
|
||||
assert_eq!(x.unit.category(), UnitType::CurrencyPerVolume);
|
||||
assert_eq!(x.unit.singular(), "USD/floz");
|
||||
|
||||
let y = eval_default("5.55 g / 64 fl oz");
|
||||
assert_eq!(y.unit.category(), UnitType::MassPerVolume);
|
||||
assert_eq!(y.unit.singular(), "g/floz");
|
||||
|
||||
// Conversion should work on the derived units:
|
||||
let z = eval_default("5.55 usd / 64 fl oz to cny/l");
|
||||
assert_eq!(z.unit.singular(), "CNY/l");
|
||||
|
||||
let w = eval_default("5.55 g / 64 fl oz to g/ml");
|
||||
assert_eq!(w.unit.singular(), "g/ml");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiply_cancels_derived_units() {
|
||||
extra_units::clear_for_tests();
|
||||
|
||||
let json = r#"{"base":"USD","rates":{"USD":1,"CNY":7.07}}"#;
|
||||
currency::register_currencies_from_exchange_rate_api_json(json).unwrap();
|
||||
|
||||
// Your example: (USD/L) * floz -> USD
|
||||
let out = eval_default("(1.48-1/3) usd / 1.25 l * 144 fl oz");
|
||||
assert_eq!(out.unit.category(), UnitType::Currency);
|
||||
assert_eq!(out.unit.singular(), "USD");
|
||||
assert!(!out.value.is_nan());
|
||||
|
||||
let assert_close = |actual: D128, expected: D128| {
|
||||
// Derived unit weights use division, so allow tiny rounding differences.
|
||||
assert!((actual - expected).abs() < d!(1e-24));
|
||||
};
|
||||
|
||||
// Cancellation sanity checks
|
||||
let usd_back = eval_default("5.55 usd / 64 fl oz * 64 fl oz");
|
||||
assert_eq!(usd_back.unit.singular(), "USD");
|
||||
assert_close(usd_back.value, d!(5.55));
|
||||
|
||||
let g_back = eval_default("5.55 g / 64 fl oz * 64 fl oz");
|
||||
assert_eq!(g_back.unit.singular(), "gram");
|
||||
assert_close(g_back.value, d!(5.55));
|
||||
}
|
||||
}
|
||||
|
||||
125
src/extra_units.rs
Normal file
125
src/extra_units.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use crate::units::{Unit, UnitType};
|
||||
use fastnum::D128;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct ExtraUnitDef {
|
||||
pub(crate) category: UnitType,
|
||||
pub(crate) weight: D128,
|
||||
pub(crate) singular: &'static str,
|
||||
pub(crate) plural: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Registry {
|
||||
by_name: HashMap<&'static str, u16>,
|
||||
units: Vec<ExtraUnitDef>,
|
||||
}
|
||||
|
||||
static REGISTRY: Lazy<RwLock<Registry>> = Lazy::new(|| RwLock::new(Registry::default()));
|
||||
|
||||
fn leak_str(s: String) -> &'static str {
|
||||
Box::leak(s.into_boxed_str())
|
||||
}
|
||||
|
||||
pub(crate) fn lookup_unit(word: &str) -> Option<Unit> {
|
||||
let reg = REGISTRY.read().ok()?;
|
||||
let id = *reg.by_name.get(word)?;
|
||||
Some(Unit::Extra(id))
|
||||
}
|
||||
|
||||
pub(crate) fn upsert_unit(
|
||||
name_lowercase: &str,
|
||||
singular: &str,
|
||||
plural: &str,
|
||||
category: UnitType,
|
||||
weight: D128,
|
||||
) -> Unit {
|
||||
let mut reg = REGISTRY
|
||||
.write()
|
||||
.expect("extra unit registry lock poisoned");
|
||||
|
||||
if let Some(existing_id) = reg.by_name.get(name_lowercase).copied() {
|
||||
let idx = existing_id as usize;
|
||||
reg.units[idx] = ExtraUnitDef {
|
||||
category,
|
||||
weight,
|
||||
singular: reg.units[idx].singular,
|
||||
plural: reg.units[idx].plural,
|
||||
};
|
||||
return Unit::Extra(existing_id);
|
||||
}
|
||||
|
||||
let id: u16 = reg
|
||||
.units
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("too many extra units registered");
|
||||
|
||||
let key = leak_str(name_lowercase.to_string());
|
||||
let singular = leak_str(singular.to_string());
|
||||
let plural = leak_str(plural.to_string());
|
||||
|
||||
reg.by_name.insert(key, id);
|
||||
reg.units.push(ExtraUnitDef {
|
||||
category,
|
||||
weight,
|
||||
singular,
|
||||
plural,
|
||||
});
|
||||
|
||||
Unit::Extra(id)
|
||||
}
|
||||
|
||||
pub(crate) fn get_category(id: u16) -> UnitType {
|
||||
let reg = REGISTRY
|
||||
.read()
|
||||
.expect("extra unit registry lock poisoned");
|
||||
reg.units
|
||||
.get(id as usize)
|
||||
.map(|u| u.category)
|
||||
.unwrap_or(UnitType::NoType)
|
||||
}
|
||||
|
||||
pub(crate) fn get_weight(id: u16) -> D128 {
|
||||
let reg = REGISTRY
|
||||
.read()
|
||||
.expect("extra unit registry lock poisoned");
|
||||
reg.units
|
||||
.get(id as usize)
|
||||
.map(|u| u.weight)
|
||||
.unwrap_or(D128::NAN)
|
||||
}
|
||||
|
||||
pub(crate) fn get_singular(id: u16) -> &'static str {
|
||||
let reg = REGISTRY
|
||||
.read()
|
||||
.expect("extra unit registry lock poisoned");
|
||||
reg.units
|
||||
.get(id as usize)
|
||||
.map(|u| u.singular)
|
||||
.unwrap_or("")
|
||||
}
|
||||
|
||||
pub(crate) fn get_plural(id: u16) -> &'static str {
|
||||
let reg = REGISTRY
|
||||
.read()
|
||||
.expect("extra unit registry lock poisoned");
|
||||
reg.units
|
||||
.get(id as usize)
|
||||
.map(|u| u.plural)
|
||||
.unwrap_or("")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn clear_for_tests() {
|
||||
let mut reg = REGISTRY
|
||||
.write()
|
||||
.expect("extra unit registry lock poisoned");
|
||||
reg.by_name.clear();
|
||||
reg.units.clear();
|
||||
}
|
||||
|
||||
|
||||
186
src/lexer.rs
186
src/lexer.rs
@ -10,6 +10,8 @@ use crate::Constant::{E, Pi};
|
||||
use crate::LexerKeyword::{In, PercentChar, Per, Mercury, Hg, PoundForce, Force, DoubleQuotes, Revolution};
|
||||
use crate::FunctionIdentifier::{Cbrt, Ceil, Cos, Exp, Abs, Floor, Ln, Log, Round, Sin, Sqrt, Tan};
|
||||
use crate::units::Unit::*;
|
||||
use crate::extra_units;
|
||||
use crate::units::UnitType;
|
||||
use unicode_segmentation::{Graphemes, UnicodeSegmentation};
|
||||
|
||||
fn is_word_char_str(input: &str) -> bool {
|
||||
@ -591,8 +593,12 @@ fn parse_word(word: &str, lexer: &mut Lexer) -> Result<(), String> {
|
||||
"f" | "fahrenheit" | "fahrenheits" => Token::Unit(Fahrenheit),
|
||||
|
||||
string => {
|
||||
if let Some(unit) = extra_units::lookup_unit(string) {
|
||||
Token::Unit(unit)
|
||||
} else {
|
||||
return Err(format!("Invalid string: {}", string));
|
||||
}
|
||||
}
|
||||
};
|
||||
lexer.tokens.push(token);
|
||||
Ok(())
|
||||
@ -903,6 +909,186 @@ pub fn lex(input: &str, remove_trailing_operator: bool) -> Result<Vec<Token>, St
|
||||
(Token::LexerKeyword(Revolution), Token::LexerKeyword(Per), Token::Unit(Minute)) => {
|
||||
tokens[token_index-2] = Token::Unit(RevolutionsPerMinute);
|
||||
},
|
||||
// composite units:
|
||||
// - currency per {mass|volume|length|area} (usd/lb, cny/kg, usd/l, usd/m, usd/m2)
|
||||
// - mass per volume (g/ml, kg/l, lb/gal)
|
||||
// - volume per mass (ml/g, l/kg)
|
||||
(Token::Unit(left_u), Token::LexerKeyword(Per), Token::Unit(right_u)) => {
|
||||
let left_cat = left_u.category();
|
||||
let right_cat = right_u.category();
|
||||
if left_cat == UnitType::Currency
|
||||
&& (right_cat == UnitType::Mass
|
||||
|| right_cat == UnitType::Volume
|
||||
|| right_cat == UnitType::Length
|
||||
|| right_cat == UnitType::Area)
|
||||
{
|
||||
let cur_upper = left_u.singular().to_ascii_uppercase();
|
||||
let cur_lower = cur_upper.to_ascii_lowercase();
|
||||
let denom_abbrev: &str = match (*right_u, right_cat) {
|
||||
// Mass
|
||||
(Milligram, UnitType::Mass) => "mg",
|
||||
(Gram, UnitType::Mass) => "g",
|
||||
(Hectogram, UnitType::Mass) => "hg",
|
||||
(Kilogram, UnitType::Mass) => "kg",
|
||||
(MetricTon, UnitType::Mass) => "t",
|
||||
(Ounce, UnitType::Mass) => "oz",
|
||||
(Pound, UnitType::Mass) => "lb",
|
||||
(Stone, UnitType::Mass) => "st",
|
||||
(ShortTon, UnitType::Mass) => "ston",
|
||||
(LongTon, UnitType::Mass) => "lton",
|
||||
|
||||
// Volume
|
||||
(Milliliter, UnitType::Volume) => "ml",
|
||||
(Centiliter, UnitType::Volume) => "cl",
|
||||
(Deciliter, UnitType::Volume) => "dl",
|
||||
(Liter, UnitType::Volume) => "l",
|
||||
(Teaspoon, UnitType::Volume) => "tsp",
|
||||
(Tablespoon, UnitType::Volume) => "tbsp",
|
||||
(FluidOunce, UnitType::Volume) => "floz",
|
||||
(Cup, UnitType::Volume) => "cup",
|
||||
(Pint, UnitType::Volume) => "pt",
|
||||
(Quart, UnitType::Volume) => "qt",
|
||||
(Gallon, UnitType::Volume) => "gal",
|
||||
(OilBarrel, UnitType::Volume) => "bbl",
|
||||
|
||||
// Length
|
||||
(Millimeter, UnitType::Length) => "mm",
|
||||
(Centimeter, UnitType::Length) => "cm",
|
||||
(Decimeter, UnitType::Length) => "dm",
|
||||
(Meter, UnitType::Length) => "m",
|
||||
(Kilometer, UnitType::Length) => "km",
|
||||
(Inch, UnitType::Length) => "in",
|
||||
(Foot, UnitType::Length) => "ft",
|
||||
(Yard, UnitType::Length) => "yd",
|
||||
(Mile, UnitType::Length) => "mi",
|
||||
(Marathon, UnitType::Length) => "marathon",
|
||||
(NauticalMile, UnitType::Length) => "nmi",
|
||||
(LightYear, UnitType::Length) => "ly",
|
||||
(LightSecond, UnitType::Length) => "lightsec",
|
||||
|
||||
// Area
|
||||
(SquareMillimeter, UnitType::Area) => "mm2",
|
||||
(SquareCentimeter, UnitType::Area) => "cm2",
|
||||
(SquareDecimeter, UnitType::Area) => "dm2",
|
||||
(SquareMeter, UnitType::Area) => "m2",
|
||||
(SquareKilometer, UnitType::Area) => "km2",
|
||||
(SquareInch, UnitType::Area) => "in2",
|
||||
(SquareFoot, UnitType::Area) => "ft2",
|
||||
(SquareYard, UnitType::Area) => "yd2",
|
||||
(SquareMile, UnitType::Area) => "mi2",
|
||||
(Are, UnitType::Area) => "a",
|
||||
(Decare, UnitType::Area) => "daa",
|
||||
(Hectare, UnitType::Area) => "ha",
|
||||
(Acre, UnitType::Area) => "acre",
|
||||
|
||||
_ => right_u.singular(),
|
||||
};
|
||||
let display = format!("{}/{}", cur_upper, denom_abbrev);
|
||||
let key = format!("{cur_lower}_per_{denom_abbrev}");
|
||||
// weight = (USD per currency unit) / (base volume-or-mass per denom unit)
|
||||
let weight = left_u.weight() / right_u.weight();
|
||||
let composite_type = match right_cat {
|
||||
UnitType::Mass => UnitType::CurrencyPerMass,
|
||||
UnitType::Volume => UnitType::CurrencyPerVolume,
|
||||
UnitType::Length => UnitType::CurrencyPerLength,
|
||||
UnitType::Area => UnitType::CurrencyPerArea,
|
||||
_ => {
|
||||
replaced = false;
|
||||
UnitType::CurrencyPerMass
|
||||
}
|
||||
};
|
||||
let composite = extra_units::upsert_unit(
|
||||
&key,
|
||||
&display,
|
||||
&display,
|
||||
composite_type,
|
||||
weight,
|
||||
);
|
||||
tokens[token_index-2] = Token::Unit(composite);
|
||||
} else if left_cat == UnitType::Mass && right_cat == UnitType::Volume {
|
||||
let mass_abbrev: &str = match *left_u {
|
||||
Milligram => "mg",
|
||||
Gram => "g",
|
||||
Hectogram => "hg",
|
||||
Kilogram => "kg",
|
||||
MetricTon => "t",
|
||||
Ounce => "oz",
|
||||
Pound => "lb",
|
||||
Stone => "st",
|
||||
ShortTon => "ston",
|
||||
LongTon => "lton",
|
||||
_ => left_u.singular(),
|
||||
};
|
||||
let vol_abbrev: &str = match *right_u {
|
||||
Milliliter => "ml",
|
||||
Centiliter => "cl",
|
||||
Deciliter => "dl",
|
||||
Liter => "l",
|
||||
Teaspoon => "tsp",
|
||||
Tablespoon => "tbsp",
|
||||
FluidOunce => "floz",
|
||||
Cup => "cup",
|
||||
Pint => "pt",
|
||||
Quart => "qt",
|
||||
Gallon => "gal",
|
||||
OilBarrel => "bbl",
|
||||
_ => right_u.singular(),
|
||||
};
|
||||
let display = format!("{}/{}", mass_abbrev, vol_abbrev);
|
||||
let key = format!("{mass_abbrev}_per_{vol_abbrev}");
|
||||
let weight = left_u.weight() / right_u.weight(); // grams per cubic-mm
|
||||
let composite = extra_units::upsert_unit(
|
||||
&key,
|
||||
&display,
|
||||
&display,
|
||||
UnitType::MassPerVolume,
|
||||
weight,
|
||||
);
|
||||
tokens[token_index-2] = Token::Unit(composite);
|
||||
} else if left_cat == UnitType::Volume && right_cat == UnitType::Mass {
|
||||
let vol_abbrev: &str = match *left_u {
|
||||
Milliliter => "ml",
|
||||
Centiliter => "cl",
|
||||
Deciliter => "dl",
|
||||
Liter => "l",
|
||||
Teaspoon => "tsp",
|
||||
Tablespoon => "tbsp",
|
||||
FluidOunce => "floz",
|
||||
Cup => "cup",
|
||||
Pint => "pt",
|
||||
Quart => "qt",
|
||||
Gallon => "gal",
|
||||
OilBarrel => "bbl",
|
||||
_ => left_u.singular(),
|
||||
};
|
||||
let mass_abbrev: &str = match *right_u {
|
||||
Milligram => "mg",
|
||||
Gram => "g",
|
||||
Hectogram => "hg",
|
||||
Kilogram => "kg",
|
||||
MetricTon => "t",
|
||||
Ounce => "oz",
|
||||
Pound => "lb",
|
||||
Stone => "st",
|
||||
ShortTon => "ston",
|
||||
LongTon => "lton",
|
||||
_ => right_u.singular(),
|
||||
};
|
||||
let display = format!("{}/{}", vol_abbrev, mass_abbrev);
|
||||
let key = format!("{vol_abbrev}_per_{mass_abbrev}");
|
||||
let weight = left_u.weight() / right_u.weight(); // cubic-mm per gram
|
||||
let composite = extra_units::upsert_unit(
|
||||
&key,
|
||||
&display,
|
||||
&display,
|
||||
UnitType::VolumePerMass,
|
||||
weight,
|
||||
);
|
||||
tokens[token_index-2] = Token::Unit(composite);
|
||||
} else {
|
||||
replaced = false;
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
replaced = false;
|
||||
},
|
||||
|
||||
14
src/lib.rs
14
src/lib.rs
@ -34,6 +34,10 @@ pub mod evaluator;
|
||||
pub mod lexer;
|
||||
#[rustfmt::skip]
|
||||
mod lookup;
|
||||
#[rustfmt::skip]
|
||||
mod extra_units;
|
||||
/// Currency rate loading / registration (USD base from exchangerate-api)
|
||||
pub mod currency;
|
||||
/// Turns [`Token`]s into an [`AstNode`](parser::AstNode)
|
||||
pub mod parser;
|
||||
/// Units, and functions you can use with them
|
||||
@ -299,6 +303,16 @@ pub fn wasm_eval(expression: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen]
|
||||
pub fn wasm_set_currency_rates(json: &str) -> String {
|
||||
console_error_panic_hook::set_once();
|
||||
match currency::register_currencies_from_exchange_rate_api_json(json) {
|
||||
Ok(n) => format!("OK ({n} currencies)"),
|
||||
Err(e) => format!("Error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
use cpc::eval;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use cpc::currency;
|
||||
use std::env;
|
||||
use std::process::exit;
|
||||
|
||||
@ -60,6 +62,12 @@ fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
// Best-effort: load currency rates so `usd`, `eur`, etc work as extra units.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let _ = currency::init_currencies_blocking();
|
||||
}
|
||||
|
||||
match eval(&expression, true, verbose) {
|
||||
Ok(answer) => {
|
||||
if !verbose {
|
||||
|
||||
362
src/units.rs
362
src/units.rs
@ -1,5 +1,6 @@
|
||||
use fastnum::{dec128 as d, D128, D256};
|
||||
use fastnum::{dec128 as d, D128};
|
||||
use crate::Number;
|
||||
use crate::extra_units;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
/// An enum of all possible unit types, like [`Length`], [`DigitalStorage`] etc.
|
||||
@ -39,6 +40,20 @@ pub enum UnitType {
|
||||
Speed,
|
||||
/// A unit of temperature, for example [`Kelvin`]
|
||||
Temperature,
|
||||
/// A currency unit, for example USD / EUR (loaded dynamically)
|
||||
Currency,
|
||||
/// A currency-per-mass unit, for example USD/lb or CNY/kg
|
||||
CurrencyPerMass,
|
||||
/// A currency-per-volume unit, for example USD/l or EUR/ml
|
||||
CurrencyPerVolume,
|
||||
/// A currency-per-length unit, for example USD/m or EUR/ft
|
||||
CurrencyPerLength,
|
||||
/// A currency-per-area unit, for example USD/m2 or EUR/ft2
|
||||
CurrencyPerArea,
|
||||
/// A mass-per-volume unit (density / concentration), e.g. g/ml
|
||||
MassPerVolume,
|
||||
/// A volume-per-mass unit (specific volume), e.g. ml/g
|
||||
VolumePerMass,
|
||||
}
|
||||
use UnitType::*;
|
||||
|
||||
@ -50,7 +65,9 @@ macro_rules! create_units {
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
/// A Unit enum. Note that it can also be [`NoUnit`].
|
||||
pub enum Unit {
|
||||
$($variant),*
|
||||
$($variant),*,
|
||||
/// Runtime-registered unit (currencies, custom units, etc)
|
||||
Extra(u16)
|
||||
}
|
||||
use Unit::*;
|
||||
|
||||
@ -59,28 +76,32 @@ macro_rules! create_units {
|
||||
match self {
|
||||
$(
|
||||
Unit::$variant => $properties.0
|
||||
),*
|
||||
),*,
|
||||
Unit::Extra(id) => extra_units::get_category(*id),
|
||||
}
|
||||
}
|
||||
pub fn weight(&self) -> D128 {
|
||||
match self {
|
||||
$(
|
||||
Unit::$variant => $properties.1
|
||||
),*
|
||||
),*,
|
||||
Unit::Extra(id) => extra_units::get_weight(*id),
|
||||
}
|
||||
}
|
||||
pub(crate) fn singular(&self) -> &str {
|
||||
match self {
|
||||
$(
|
||||
Unit::$variant => $properties.2
|
||||
),*
|
||||
),*,
|
||||
Unit::Extra(id) => extra_units::get_singular(*id),
|
||||
}
|
||||
}
|
||||
pub(crate) fn plural(&self) -> &str {
|
||||
match self {
|
||||
$(
|
||||
Unit::$variant => $properties.3
|
||||
),*
|
||||
),*,
|
||||
Unit::Extra(id) => extra_units::get_plural(*id),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -549,6 +570,49 @@ pub fn multiply(left: Number, right: Number) -> Result<Number, String> {
|
||||
actual_multiply(left, right, false)
|
||||
}
|
||||
|
||||
fn parse_currency_numerator(unit: Unit) -> Option<Unit> {
|
||||
let code_lower = unit.singular().split('/').next()?.to_ascii_lowercase();
|
||||
extra_units::lookup_unit(&code_lower)
|
||||
}
|
||||
|
||||
fn parse_mass_numerator(unit: Unit) -> Option<Unit> {
|
||||
let s = unit.singular().split('/').next()?.to_ascii_lowercase();
|
||||
let u = match s.as_str() {
|
||||
"mg" => Milligram,
|
||||
"g" => Gram,
|
||||
"hg" => Hectogram,
|
||||
"kg" => Kilogram,
|
||||
"t" => MetricTon,
|
||||
"oz" => Ounce,
|
||||
"lb" => Pound,
|
||||
"st" => Stone,
|
||||
"ston" => ShortTon,
|
||||
"lton" => LongTon,
|
||||
_ => return None,
|
||||
};
|
||||
Some(u)
|
||||
}
|
||||
|
||||
fn parse_volume_numerator(unit: Unit) -> Option<Unit> {
|
||||
let s = unit.singular().split('/').next()?.to_ascii_lowercase();
|
||||
let u = match s.as_str() {
|
||||
"ml" => Milliliter,
|
||||
"cl" => Centiliter,
|
||||
"dl" => Deciliter,
|
||||
"l" => Liter,
|
||||
"tsp" => Teaspoon,
|
||||
"tbsp" => Tablespoon,
|
||||
"floz" => FluidOunce,
|
||||
"cup" => Cup,
|
||||
"pt" => Pint,
|
||||
"qt" => Quart,
|
||||
"gal" => Gallon,
|
||||
"bbl" => OilBarrel,
|
||||
_ => return None,
|
||||
};
|
||||
Some(u)
|
||||
}
|
||||
|
||||
fn actual_multiply(left: Number, right: Number, swapped: bool) -> Result<Number, String> {
|
||||
let lcat = left.unit.category();
|
||||
let rcat = right.unit.category();
|
||||
@ -628,6 +692,81 @@ fn actual_multiply(left: Number, right: Number, swapped: bool) -> Result<Number,
|
||||
};
|
||||
let data_storage = Number::new(result, Bit);
|
||||
Ok(convert(data_storage, final_unit)?)
|
||||
} else if lcat == CurrencyPerVolume && rcat == Volume {
|
||||
// (currency per volume) * volume = currency
|
||||
let rate_usd_per_mm3 = left.value * left.unit.weight();
|
||||
let volume_mm3 = right.value * right.unit.weight();
|
||||
let usd_amount = rate_usd_per_mm3 * volume_mm3;
|
||||
|
||||
let usd_unit = extra_units::lookup_unit("usd")
|
||||
.ok_or("Currency rates not initialized (missing USD)")?;
|
||||
|
||||
let mut result = Number::new(usd_amount, usd_unit);
|
||||
if let Some(cur_unit) = parse_currency_numerator(left.unit) {
|
||||
result = convert(result, cur_unit)?;
|
||||
}
|
||||
Ok(result)
|
||||
} else if lcat == CurrencyPerMass && rcat == Mass {
|
||||
let rate_usd_per_g = left.value * left.unit.weight();
|
||||
let grams = right.value * right.unit.weight();
|
||||
let usd_amount = rate_usd_per_g * grams;
|
||||
|
||||
let usd_unit = extra_units::lookup_unit("usd")
|
||||
.ok_or("Currency rates not initialized (missing USD)")?;
|
||||
|
||||
let mut result = Number::new(usd_amount, usd_unit);
|
||||
if let Some(cur_unit) = parse_currency_numerator(left.unit) {
|
||||
result = convert(result, cur_unit)?;
|
||||
}
|
||||
Ok(result)
|
||||
} else if lcat == CurrencyPerLength && rcat == Length {
|
||||
let rate_usd_per_mm = left.value * left.unit.weight();
|
||||
let mm = right.value * right.unit.weight();
|
||||
let usd_amount = rate_usd_per_mm * mm;
|
||||
|
||||
let usd_unit = extra_units::lookup_unit("usd")
|
||||
.ok_or("Currency rates not initialized (missing USD)")?;
|
||||
|
||||
let mut result = Number::new(usd_amount, usd_unit);
|
||||
if let Some(cur_unit) = parse_currency_numerator(left.unit) {
|
||||
result = convert(result, cur_unit)?;
|
||||
}
|
||||
Ok(result)
|
||||
} else if lcat == CurrencyPerArea && rcat == Area {
|
||||
let rate_usd_per_mm2 = left.value * left.unit.weight();
|
||||
let mm2 = right.value * right.unit.weight();
|
||||
let usd_amount = rate_usd_per_mm2 * mm2;
|
||||
|
||||
let usd_unit = extra_units::lookup_unit("usd")
|
||||
.ok_or("Currency rates not initialized (missing USD)")?;
|
||||
|
||||
let mut result = Number::new(usd_amount, usd_unit);
|
||||
if let Some(cur_unit) = parse_currency_numerator(left.unit) {
|
||||
result = convert(result, cur_unit)?;
|
||||
}
|
||||
Ok(result)
|
||||
} else if lcat == MassPerVolume && rcat == Volume {
|
||||
// (mass per volume) * volume = mass
|
||||
let rate_g_per_mm3 = left.value * left.unit.weight();
|
||||
let volume_mm3 = right.value * right.unit.weight();
|
||||
let grams = rate_g_per_mm3 * volume_mm3;
|
||||
|
||||
let mut result = Number::new(grams, Gram);
|
||||
if let Some(mass_unit) = parse_mass_numerator(left.unit) {
|
||||
result = convert(result, mass_unit)?;
|
||||
}
|
||||
Ok(result)
|
||||
} else if lcat == VolumePerMass && rcat == Mass {
|
||||
// (volume per mass) * mass = volume
|
||||
let rate_mm3_per_g = left.value * left.unit.weight();
|
||||
let grams = right.value * right.unit.weight();
|
||||
let mm3 = rate_mm3_per_g * grams;
|
||||
|
||||
let mut result = Number::new(mm3, CubicMillimeter);
|
||||
if let Some(vol_unit) = parse_volume_numerator(left.unit) {
|
||||
result = convert(result, vol_unit)?;
|
||||
}
|
||||
Ok(result)
|
||||
} else if lcat == Voltage && rcat == ElectricCurrent {
|
||||
// 1 volt * 1 ampere = 1 watt
|
||||
let result = (left.value * left.unit.weight()) * (right.value * right.unit.weight());
|
||||
@ -712,6 +851,201 @@ pub fn divide(left: Number, right: Number) -> Result<Number, String> {
|
||||
let bits_per_second = convert(right, BitsPerSecond)?;
|
||||
let seconds = Number::new(bits.value / bits_per_second.value, Second);
|
||||
Ok(to_ideal_unit(seconds))
|
||||
} else if lcat == Currency && rcat == Volume {
|
||||
// 5 usd / 64 floz => usd/floz (convertible to usd/l, cny/l, etc)
|
||||
let cur_upper = left.unit.singular().to_ascii_uppercase();
|
||||
let cur_lower = cur_upper.to_ascii_lowercase();
|
||||
let denom_abbrev: &str = match right.unit {
|
||||
Milliliter => "ml",
|
||||
Centiliter => "cl",
|
||||
Deciliter => "dl",
|
||||
Liter => "l",
|
||||
Teaspoon => "tsp",
|
||||
Tablespoon => "tbsp",
|
||||
FluidOunce => "floz",
|
||||
Cup => "cup",
|
||||
Pint => "pt",
|
||||
Quart => "qt",
|
||||
Gallon => "gal",
|
||||
OilBarrel => "bbl",
|
||||
_ => right.unit.singular(),
|
||||
};
|
||||
let display = format!("{}/{}", cur_upper, denom_abbrev);
|
||||
let key = format!("{cur_lower}_per_{denom_abbrev}");
|
||||
let composite = crate::extra_units::upsert_unit(
|
||||
&key,
|
||||
&display,
|
||||
&display,
|
||||
CurrencyPerVolume,
|
||||
left.unit.weight() / right.unit.weight(),
|
||||
);
|
||||
Ok(Number::new(left.value / right.value, composite))
|
||||
} else if lcat == Currency && rcat == Mass {
|
||||
// usd/kg etc
|
||||
let cur_upper = left.unit.singular().to_ascii_uppercase();
|
||||
let cur_lower = cur_upper.to_ascii_lowercase();
|
||||
let denom_abbrev: &str = match right.unit {
|
||||
Milligram => "mg",
|
||||
Gram => "g",
|
||||
Hectogram => "hg",
|
||||
Kilogram => "kg",
|
||||
MetricTon => "t",
|
||||
Ounce => "oz",
|
||||
Pound => "lb",
|
||||
Stone => "st",
|
||||
ShortTon => "ston",
|
||||
LongTon => "lton",
|
||||
_ => right.unit.singular(),
|
||||
};
|
||||
let display = format!("{}/{}", cur_upper, denom_abbrev);
|
||||
let key = format!("{cur_lower}_per_{denom_abbrev}");
|
||||
let composite = crate::extra_units::upsert_unit(
|
||||
&key,
|
||||
&display,
|
||||
&display,
|
||||
CurrencyPerMass,
|
||||
left.unit.weight() / right.unit.weight(),
|
||||
);
|
||||
Ok(Number::new(left.value / right.value, composite))
|
||||
} else if lcat == Currency && rcat == Length {
|
||||
// usd/m, etc
|
||||
let cur_upper = left.unit.singular().to_ascii_uppercase();
|
||||
let cur_lower = cur_upper.to_ascii_lowercase();
|
||||
let denom_abbrev: &str = match right.unit {
|
||||
Millimeter => "mm",
|
||||
Centimeter => "cm",
|
||||
Decimeter => "dm",
|
||||
Meter => "m",
|
||||
Kilometer => "km",
|
||||
Inch => "in",
|
||||
Foot => "ft",
|
||||
Yard => "yd",
|
||||
Mile => "mi",
|
||||
NauticalMile => "nmi",
|
||||
LightYear => "ly",
|
||||
LightSecond => "lightsec",
|
||||
_ => right.unit.singular(),
|
||||
};
|
||||
let display = format!("{}/{}", cur_upper, denom_abbrev);
|
||||
let key = format!("{cur_lower}_per_{denom_abbrev}");
|
||||
let composite = crate::extra_units::upsert_unit(
|
||||
&key,
|
||||
&display,
|
||||
&display,
|
||||
CurrencyPerLength,
|
||||
left.unit.weight() / right.unit.weight(),
|
||||
);
|
||||
Ok(Number::new(left.value / right.value, composite))
|
||||
} else if lcat == Currency && rcat == Area {
|
||||
// usd/m2, etc
|
||||
let cur_upper = left.unit.singular().to_ascii_uppercase();
|
||||
let cur_lower = cur_upper.to_ascii_lowercase();
|
||||
let denom_abbrev: &str = match right.unit {
|
||||
SquareMillimeter => "mm2",
|
||||
SquareCentimeter => "cm2",
|
||||
SquareDecimeter => "dm2",
|
||||
SquareMeter => "m2",
|
||||
SquareKilometer => "km2",
|
||||
SquareInch => "in2",
|
||||
SquareFoot => "ft2",
|
||||
SquareYard => "yd2",
|
||||
SquareMile => "mi2",
|
||||
Are => "a",
|
||||
Decare => "daa",
|
||||
Hectare => "ha",
|
||||
Acre => "acre",
|
||||
_ => right.unit.singular(),
|
||||
};
|
||||
let display = format!("{}/{}", cur_upper, denom_abbrev);
|
||||
let key = format!("{cur_lower}_per_{denom_abbrev}");
|
||||
let composite = crate::extra_units::upsert_unit(
|
||||
&key,
|
||||
&display,
|
||||
&display,
|
||||
CurrencyPerArea,
|
||||
left.unit.weight() / right.unit.weight(),
|
||||
);
|
||||
Ok(Number::new(left.value / right.value, composite))
|
||||
} else if lcat == Mass && rcat == Volume {
|
||||
// 5 g / 64 floz => g/floz (convertible to g/ml, kg/l, etc)
|
||||
let mass_abbrev: &str = match left.unit {
|
||||
Milligram => "mg",
|
||||
Gram => "g",
|
||||
Hectogram => "hg",
|
||||
Kilogram => "kg",
|
||||
MetricTon => "t",
|
||||
Ounce => "oz",
|
||||
Pound => "lb",
|
||||
Stone => "st",
|
||||
ShortTon => "ston",
|
||||
LongTon => "lton",
|
||||
_ => left.unit.singular(),
|
||||
};
|
||||
let vol_abbrev: &str = match right.unit {
|
||||
Milliliter => "ml",
|
||||
Centiliter => "cl",
|
||||
Deciliter => "dl",
|
||||
Liter => "l",
|
||||
Teaspoon => "tsp",
|
||||
Tablespoon => "tbsp",
|
||||
FluidOunce => "floz",
|
||||
Cup => "cup",
|
||||
Pint => "pt",
|
||||
Quart => "qt",
|
||||
Gallon => "gal",
|
||||
OilBarrel => "bbl",
|
||||
_ => right.unit.singular(),
|
||||
};
|
||||
let display = format!("{}/{}", mass_abbrev, vol_abbrev);
|
||||
let key = format!("{mass_abbrev}_per_{vol_abbrev}");
|
||||
let composite = crate::extra_units::upsert_unit(
|
||||
&key,
|
||||
&display,
|
||||
&display,
|
||||
MassPerVolume,
|
||||
left.unit.weight() / right.unit.weight(),
|
||||
);
|
||||
Ok(Number::new(left.value / right.value, composite))
|
||||
} else if lcat == Volume && rcat == Mass {
|
||||
// ml/g etc
|
||||
let vol_abbrev: &str = match left.unit {
|
||||
Milliliter => "ml",
|
||||
Centiliter => "cl",
|
||||
Deciliter => "dl",
|
||||
Liter => "l",
|
||||
Teaspoon => "tsp",
|
||||
Tablespoon => "tbsp",
|
||||
FluidOunce => "floz",
|
||||
Cup => "cup",
|
||||
Pint => "pt",
|
||||
Quart => "qt",
|
||||
Gallon => "gal",
|
||||
OilBarrel => "bbl",
|
||||
_ => left.unit.singular(),
|
||||
};
|
||||
let mass_abbrev: &str = match right.unit {
|
||||
Milligram => "mg",
|
||||
Gram => "g",
|
||||
Hectogram => "hg",
|
||||
Kilogram => "kg",
|
||||
MetricTon => "t",
|
||||
Ounce => "oz",
|
||||
Pound => "lb",
|
||||
Stone => "st",
|
||||
ShortTon => "ston",
|
||||
LongTon => "lton",
|
||||
_ => right.unit.singular(),
|
||||
};
|
||||
let display = format!("{}/{}", vol_abbrev, mass_abbrev);
|
||||
let key = format!("{vol_abbrev}_per_{mass_abbrev}");
|
||||
let composite = crate::extra_units::upsert_unit(
|
||||
&key,
|
||||
&display,
|
||||
&display,
|
||||
VolumePerMass,
|
||||
left.unit.weight() / right.unit.weight(),
|
||||
);
|
||||
Ok(Number::new(left.value / right.value, composite))
|
||||
} else if lcat == Power && rcat == ElectricCurrent {
|
||||
// 1 watt / 1 ampere = 1 volt
|
||||
let result = (left.value * left.unit.weight()) / (right.value * right.unit.weight());
|
||||
@ -754,15 +1088,6 @@ pub fn modulo(left: Number, right: Number) -> Result<Number, String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn do_pow(left: D128, right: D128) -> D128 {
|
||||
// Do pow with d256 for higher accuracy
|
||||
let left = D256::try_from(left.transmute()).unwrap();
|
||||
let right = D256::try_from(right.transmute()).unwrap();
|
||||
let result = left.pow(right);
|
||||
let result_d128: D128 = result.transmute();
|
||||
result_d128
|
||||
}
|
||||
|
||||
/// Returns a [`Number`] to the power of another [`Number`]
|
||||
///
|
||||
/// - If you take [`Length`] to the power of [`NoType`], the result has a unit of [`Area`].
|
||||
@ -770,20 +1095,21 @@ fn do_pow(left: D128, right: D128) -> D128 {
|
||||
/// - If you take [`Length`] to the power of [`Area`], the result has a unit of [`Volume`]
|
||||
/// - etc.
|
||||
pub fn pow(left: Number, right: Number) -> Result<Number, String> {
|
||||
// I tried converting `right` to use powi, but somehow that was slower
|
||||
let lcat = left.unit.category();
|
||||
let rcat = left.unit.category();
|
||||
if left.unit == NoUnit && right.unit == NoUnit {
|
||||
// 3 ^ 2
|
||||
Ok(Number::new(do_pow(left.value, right.value), left.unit))
|
||||
Ok(Number::new(left.value.pow(right.value), left.unit))
|
||||
} else if right.value == d!(1) && right.unit == NoUnit {
|
||||
Ok(left)
|
||||
} else if lcat == Length && right.unit == NoUnit && right.value == d!(2) {
|
||||
// x km ^ 2
|
||||
let result = do_pow(left.value * left.unit.weight(), right.value);
|
||||
let result = (left.value * left.unit.weight()).pow(right.value);
|
||||
Ok(to_ideal_unit(Number::new(result, SquareMillimeter)))
|
||||
} else if lcat == Length && right.unit == NoUnit && right.value == d!(3) {
|
||||
// x km ^ 3
|
||||
let result = do_pow(left.value * left.unit.weight(), right.value);
|
||||
let result = (left.value * left.unit.weight()).pow(right.value);
|
||||
Ok(to_ideal_unit(Number::new(result, CubicMillimeter)))
|
||||
} else if lcat == Length && rcat == Length && right.value == d!(1) {
|
||||
// x km ^ 1 km
|
||||
|
||||
96
web/package-lock.json
generated
96
web/package-lock.json
generated
@ -10,11 +10,11 @@
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/vite": "^4.1.8",
|
||||
"cpc": "file:../pkg",
|
||||
"svelte": "^5.33.10",
|
||||
"svelte": "^5.33.13",
|
||||
"svelte-check": "^4.2.1",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tailwindcss": "^4.1.8",
|
||||
"typescript": "^5.8.3",
|
||||
"vercel": "^42.2.0",
|
||||
"vercel": "^42.3.0",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-top-level-await": "^1.5.0",
|
||||
"vite-plugin-wasm": "^3.4.1",
|
||||
@ -23,7 +23,7 @@
|
||||
},
|
||||
"../pkg": {
|
||||
"name": "cpc",
|
||||
"version": "2.0.0",
|
||||
"version": "3.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@ -1752,9 +1752,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.15.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.27.tgz",
|
||||
"integrity": "sha512-5fF+eu5mwihV2BeVtX5vijhdaZOfkQTATrePEaXTcKqI16LhJ7gi2/Vhd9OZM0UojcdmiOCVg5rrax+i1MdoQQ==",
|
||||
"version": "22.15.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz",
|
||||
"integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@ -1984,9 +1984,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/gatsby-plugin-vercel-builder": {
|
||||
"version": "2.0.82",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.0.82.tgz",
|
||||
"integrity": "sha512-KlJcfdS+pEjXQ7mmEv67RorxqUwbtRWB7RN8F/nPn9eUaoNCirvfgfO83CJyljWEOoaXcBv3U9rGzs/YtKjF/A==",
|
||||
"version": "2.0.83",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.0.83.tgz",
|
||||
"integrity": "sha512-SSuCIHZmTTMc6HEnipkAkW2+cbEmCHGnOyF3uvOfEzjiQX9zB50ziq0DV3yPltSxp37MFkYKDUeWJe2ESXPJaA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@sinclair/typebox": "0.25.24",
|
||||
@ -2040,13 +2040,13 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@vercel/hydrogen": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/hydrogen/-/hydrogen-1.2.1.tgz",
|
||||
"integrity": "sha512-pHHiYkvCI+SkRHVDzBsp0HoV3pLymIJIFmRsifCyWryBzhFIg4nA8XUuG2cHGJxOTrnnc85o1Xw6DyagDT34dQ==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/hydrogen/-/hydrogen-1.2.2.tgz",
|
||||
"integrity": "sha512-PRA3r1/ZRcklGgs/hczprQZ27jX9Avyq/iEbtmzAFNbFovkTlkE0Wy93pVKJfJ4ISCBzBgUSMktX9+6wgjs32A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@vercel/static-config": "3.1.0",
|
||||
"@vercel/static-config": "3.1.1",
|
||||
"ts-morph": "12.0.0"
|
||||
}
|
||||
},
|
||||
@ -2088,9 +2088,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/nft": {
|
||||
"version": "0.29.3",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.3.tgz",
|
||||
"integrity": "sha512-aVV0E6vJpuvImiMwU1/5QKkw2N96BRFE7mBYGS7FhXUoS6V7SarQ+8tuj33o7ofECz8JtHpmQ9JW+oVzOoB7MA==",
|
||||
"version": "0.29.4",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.4.tgz",
|
||||
"integrity": "sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -2115,9 +2115,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/node": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.2.0.tgz",
|
||||
"integrity": "sha512-E8LpLhU13v7tKU+RxXESYtQZDVkt4CepK6uNN7CrzZzEuZqphkC8QrXg1Yrdb9kt8NgJaFjGeBtp9BkiRRC8WA==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.2.1.tgz",
|
||||
"integrity": "sha512-0+YV01grkqfHIHhmWeCXWmgeP6GsuzXtgWBri3+qESwfAZ6dOTBG4GJp9z2E7sEi+wP60S0/eNGRj8z77uk3JQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@ -2128,7 +2128,7 @@
|
||||
"@vercel/build-utils": "10.6.0",
|
||||
"@vercel/error-utils": "2.0.3",
|
||||
"@vercel/nft": "0.29.2",
|
||||
"@vercel/static-config": "3.1.0",
|
||||
"@vercel/static-config": "3.1.1",
|
||||
"async-listen": "3.0.0",
|
||||
"cjs-module-lexer": "1.2.3",
|
||||
"edge-runtime": "2.5.9",
|
||||
@ -2267,14 +2267,14 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@vercel/redwood": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/redwood/-/redwood-2.3.2.tgz",
|
||||
"integrity": "sha512-nAzihiYucoz7nzQNW3DkLnaeyFSujkEI8mEBb5LUM2iUXHCvSIqhh/cCu17KZg10aho/R5YOIWLoPPDMkMYFzA==",
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/redwood/-/redwood-2.3.3.tgz",
|
||||
"integrity": "sha512-9Dfith+CYNNt/5Mkrklu7xWroWgSJVR4uh7mwu/2IvuCiJMNa24ReR9xtQNyGFAwAjdeweQ/nHfImz+12ORfpQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@vercel/nft": "0.29.2",
|
||||
"@vercel/static-config": "3.1.0",
|
||||
"@vercel/static-config": "3.1.1",
|
||||
"semver": "6.3.1",
|
||||
"ts-morph": "12.0.0"
|
||||
}
|
||||
@ -2317,15 +2317,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/remix-builder": {
|
||||
"version": "5.4.8",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/remix-builder/-/remix-builder-5.4.8.tgz",
|
||||
"integrity": "sha512-lQql/nohy4CFr+fo+eOAWqh0PSP8dJuwhzlwH6J8QB9nUZd5PvaJg165y5+Dz5I3hBxYfTQCqBvqmMp/NZCzzQ==",
|
||||
"version": "5.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/remix-builder/-/remix-builder-5.4.9.tgz",
|
||||
"integrity": "sha512-+fWdMjVI6bO0GUBJbw2seBDnLvPi2dd9aBQHVG2TCbJobBPfXgyEMgRWDS+4gjhXn4jLatX4B5C5iJykkeMqNQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@vercel/error-utils": "2.0.3",
|
||||
"@vercel/nft": "0.29.2",
|
||||
"@vercel/static-config": "3.1.0",
|
||||
"@vercel/static-config": "3.1.1",
|
||||
"path-to-regexp": "6.1.0",
|
||||
"path-to-regexp-updated": "npm:path-to-regexp@6.3.0",
|
||||
"ts-morph": "12.0.0"
|
||||
@ -2366,22 +2366,22 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@vercel/static-build": {
|
||||
"version": "2.7.8",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.7.8.tgz",
|
||||
"integrity": "sha512-2mx8QVogdtmnVK26wf3mBYwBEK5GVIcxjuBN/JVu0ozX3UskHvEZwf+BJo7J0RzwSh37mR4ERLP90qpCuWLM8g==",
|
||||
"version": "2.7.9",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.7.9.tgz",
|
||||
"integrity": "sha512-0AuRrNAE0wntRjZo0CrmQTwvRVO5/7jtcSShgkAKcx2CsPphDjBh2Ah6Kcwb2w9inqJr5Bpg0pxi/Y915hj2oQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@vercel/gatsby-plugin-vercel-analytics": "1.0.11",
|
||||
"@vercel/gatsby-plugin-vercel-builder": "2.0.82",
|
||||
"@vercel/static-config": "3.1.0",
|
||||
"@vercel/gatsby-plugin-vercel-builder": "2.0.83",
|
||||
"@vercel/static-config": "3.1.1",
|
||||
"ts-morph": "12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vercel/static-config": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.1.0.tgz",
|
||||
"integrity": "sha512-NUUdlTvqmCrg+/Kd/T1yONDym3bMiUJW+wlaZoqgUNMANvmIkO10/lk/2fFRQtGkYaPlnM+TUrL+U2FghccHQg==",
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.1.1.tgz",
|
||||
"integrity": "sha512-IRtKnm9N1Uqd2ayIbLPjRtdwcl1GTWvqF1PuEVNm9O43kmoI+m9VpGlW8oga+5LQq1LmJ2Y67zHr7NbjrH1rrw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@ -5045,9 +5045,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/svelte": {
|
||||
"version": "5.33.10",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.10.tgz",
|
||||
"integrity": "sha512-/yArPQIBoQS2p86LKnvJywOXkVHeEXnFgrDPSxkEfIAEkykopYuy2bF6UUqHG4IbZlJD6OurLxJT8Kn7kTk9WA==",
|
||||
"version": "5.33.13",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.13.tgz",
|
||||
"integrity": "sha512-uT3BAPpHGaJqpOgdwJwIK7P4JkBkSS0vylbaRXxQjt1gr+DZ9BiPkhmbZw3ql8LJofUyz5XyrzzQDgQQdfP86Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -5375,23 +5375,23 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vercel": {
|
||||
"version": "42.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vercel/-/vercel-42.2.0.tgz",
|
||||
"integrity": "sha512-a733x8TmsOagZoYJyVUxMpNI1UOFmp+dw/1/j2iH+ZmkEbYfiEJo96TtqgqPgzX2qF6iEO63i0wDwwFI6aZssA==",
|
||||
"version": "42.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vercel/-/vercel-42.3.0.tgz",
|
||||
"integrity": "sha512-hLiqfcvsjI7IRm5gYIAE7d7wIAqnn797oyDdMkaw76pAQh6aFqvrw4EYcByuxuCSe/5bwck5LNcJ3neXeceGbQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@vercel/build-utils": "10.6.0",
|
||||
"@vercel/fun": "1.1.6",
|
||||
"@vercel/go": "3.2.1",
|
||||
"@vercel/hydrogen": "1.2.1",
|
||||
"@vercel/hydrogen": "1.2.2",
|
||||
"@vercel/next": "4.8.0",
|
||||
"@vercel/node": "5.2.0",
|
||||
"@vercel/node": "5.2.1",
|
||||
"@vercel/python": "4.7.2",
|
||||
"@vercel/redwood": "2.3.2",
|
||||
"@vercel/remix-builder": "5.4.8",
|
||||
"@vercel/redwood": "2.3.3",
|
||||
"@vercel/remix-builder": "5.4.9",
|
||||
"@vercel/ruby": "2.2.0",
|
||||
"@vercel/static-build": "2.7.8",
|
||||
"@vercel/static-build": "2.7.9",
|
||||
"chokidar": "4.0.0",
|
||||
"jose": "5.9.6"
|
||||
},
|
||||
|
||||
@ -16,11 +16,11 @@
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/vite": "^4.1.8",
|
||||
"cpc": "file:../pkg",
|
||||
"svelte": "^5.33.10",
|
||||
"svelte": "^5.33.13",
|
||||
"svelte-check": "^4.2.1",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tailwindcss": "^4.1.8",
|
||||
"typescript": "^5.8.3",
|
||||
"vercel": "^42.2.0",
|
||||
"vercel": "^42.3.0",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-top-level-await": "^1.5.0",
|
||||
"vite-plugin-wasm": "^3.4.1",
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import "../app.css";
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
{@render children()}
|
||||
<slot />
|
||||
|
||||
@ -1,2 +1 @@
|
||||
export const ssr = false;
|
||||
export const prerender = true;
|
||||
|
||||
@ -1,28 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { check_shortcut } from "$lib/helpers";
|
||||
import { wasm_eval } from "cpc";
|
||||
import { flip } from "svelte/animate";
|
||||
import { fly } from "svelte/transition";
|
||||
// Has to be dynamically imported for prerendering to work
|
||||
// https://github.com/sveltejs/svelte/issues/13155
|
||||
const cpc_promise = import("cpc");
|
||||
let cpc: typeof import("cpc") | undefined;
|
||||
cpc_promise.then((mod) => {
|
||||
cpc = mod;
|
||||
});
|
||||
|
||||
let input = $state("");
|
||||
let output = $derived.by(() => {
|
||||
try {
|
||||
if (input.trim().length === 0) {
|
||||
function wasm_eval(input: string) {
|
||||
if (!cpc || input.trim().length === 0) {
|
||||
return "";
|
||||
}
|
||||
return wasm_eval(input);
|
||||
try {
|
||||
return cpc.wasm_eval(input);
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let input = $state("");
|
||||
let output = $derived(wasm_eval(input));
|
||||
let saved_queries: { id: number; in: string; out: string }[] = $state([]);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>cpc</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Text calculator with support for units and conversion"
|
||||
/>
|
||||
</svelte:head>
|
||||
|
||||
<main class="w-full px-4 lg:px-8 text-base lg:text-lg">
|
||||
<nav class="flex items-center justify-between py-4 lg:py-6">
|
||||
<h1 class="text-3xl font-bold text-amber-600 dark:text-amber-400">
|
||||
cpc
|
||||
</h1>
|
||||
<h1 class="text-3xl font-bold text-amber-600 dark:text-amber-400">cpc</h1>
|
||||
<a
|
||||
href="https://github.com/probablykasper/cpc"
|
||||
aria-label="GitHub repository"
|
||||
@ -46,30 +60,28 @@
|
||||
>
|
||||
</a>
|
||||
</nav>
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
type="text"
|
||||
class="border border-gray-500/50 w-full rounded-lg px-3 py-2 outline-none"
|
||||
bind:value={input}
|
||||
onkeydown={(e) => {
|
||||
if (check_shortcut(e, "Enter")) {
|
||||
onkeydown={async (e) => {
|
||||
const input_el = e.currentTarget;
|
||||
const input = e.currentTarget.value;
|
||||
let out;
|
||||
try {
|
||||
out = wasm_eval(e.currentTarget.value);
|
||||
} catch (e) {
|
||||
out = "";
|
||||
}
|
||||
if (check_shortcut(e, "Enter")) {
|
||||
const out = wasm_eval(input)
|
||||
console.log(out);
|
||||
saved_queries.unshift({
|
||||
id: saved_queries.length,
|
||||
in: input,
|
||||
out,
|
||||
});
|
||||
e.currentTarget.value = "";
|
||||
input_el.value = "";
|
||||
output = "";
|
||||
}
|
||||
}}
|
||||
placeholder="10km/h * 1 decade in light seconds"
|
||||
autofocus
|
||||
/>
|
||||
<div class="pt-1 leading-tight">
|
||||
<div class="px-3 py-2">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user