Compare commits
246 Commits
0.7.1-r202
...
main
Author | SHA1 | Date |
---|---|---|
Tony Garnock-Jones | 685302f547 | |
Tony Garnock-Jones | f83e67899e | |
Tony Garnock-Jones | f2331c0e1e | |
Tony Garnock-Jones | f628e7d31f | |
Tony Garnock-Jones | b767fa4eb0 | |
Tony Garnock-Jones | 58110e7c0c | |
Tony Garnock-Jones | 58ebc93eb5 | |
Tony Garnock-Jones | f18ba9c9d4 | |
Tony Garnock-Jones | 87ecdb7efe | |
Tony Garnock-Jones | 0533840bc0 | |
Tony Garnock-Jones | 77c16df89b | |
Tony Garnock-Jones | 35e6ba2e82 | |
Tony Garnock-Jones | 536e32b0e8 | |
Tony Garnock-Jones | 9192bdea7e | |
Tony Garnock-Jones | 19ac8d16c8 | |
Tony Garnock-Jones | 7f284a9d52 | |
Tony Garnock-Jones | cadf54b927 | |
Tony Garnock-Jones | a8b300e57d | |
Tony Garnock-Jones | 4e5e64f0a6 | |
Tony Garnock-Jones | c8ce125192 | |
Tony Garnock-Jones | c9fa9c590b | |
Tony Garnock-Jones | d568fc56ce | |
Tony Garnock-Jones | 42f4672446 | |
Tony Garnock-Jones | 1c86d8b7c5 | |
Tony Garnock-Jones | 64c1090938 | |
Tony Garnock-Jones | dc61963e16 | |
Tony Garnock-Jones | a33786e469 | |
Tony Garnock-Jones | 23ba2e5a59 | |
Tony Garnock-Jones | 7b8e0ff4b6 | |
Tony Garnock-Jones | 3f7819fafa | |
Tony Garnock-Jones | f5d76a847b | |
Tony Garnock-Jones | 443406a7d7 | |
Tony Garnock-Jones | 05103e9825 | |
Tony Garnock-Jones | 4f75d6d5a3 | |
Tony Garnock-Jones | 07b7739d00 | |
Tony Garnock-Jones | 8f3d22adf1 | |
Tony Garnock-Jones | 8222675b6b | |
Tony Garnock-Jones | fca4b3a22e | |
Tony Garnock-Jones | c5dd2d749a | |
Tony Garnock-Jones | c986ca76cf | |
Tony Garnock-Jones | 3e67c75427 | |
Tony Garnock-Jones | 6bc159e3c6 | |
Tony Garnock-Jones | 99d1acdec7 | |
Tony Garnock-Jones | 3093b89f0d | |
Tony Garnock-Jones | 00c0de40ea | |
Tony Garnock-Jones | 7657952993 | |
Tony Garnock-Jones | 9ecbd0bdd1 | |
Tony Garnock-Jones | 297e1630a8 | |
Tony Garnock-Jones | 85ca0b6c0a | |
Tony Garnock-Jones | 7c9c410a9b | |
Tony Garnock-Jones | cbbc6c50c0 | |
Tony Garnock-Jones | eb4f456550 | |
Tony Garnock-Jones | 4f4ff6e108 | |
Tony Garnock-Jones | 055a7f90e9 | |
Tony Garnock-Jones | b2f6149042 | |
Tony Garnock-Jones | 1bd4a3cdb4 | |
Tony Garnock-Jones | dc0ddf95dd | |
Tony Garnock-Jones | eeace57670 | |
Tony Garnock-Jones | 0aa39da971 | |
Tony Garnock-Jones | f0815ce4eb | |
Tony Garnock-Jones | afba8a0bff | |
Tony Garnock-Jones | d9ec3bfb14 | |
Tony Garnock-Jones | 95ac4b13df | |
Tony Garnock-Jones | 3eeee5f090 | |
Tony Garnock-Jones | aeacce22fc | |
Tony Garnock-Jones | 0726684ab5 | |
Tony Garnock-Jones | f74c4ebaf0 | |
Tony Garnock-Jones | 48a063539a | |
Tony Garnock-Jones | db96fcc95a | |
Tony Garnock-Jones | cee4a25460 | |
Tony Garnock-Jones | 7948ad4260 | |
Tony Garnock-Jones | 3d3c79e617 | |
Tony Garnock-Jones | b925b53756 | |
Tony Garnock-Jones | c0289e0a05 | |
Tony Garnock-Jones | 41189f551d | |
Tony Garnock-Jones | cc8313cf25 | |
Tony Garnock-Jones | bfbff65bb6 | |
Tony Garnock-Jones | 442a987523 | |
Tony Garnock-Jones | f45b136ef5 | |
Tony Garnock-Jones | 73c6593f84 | |
Tony Garnock-Jones | a9e226f759 | |
Tony Garnock-Jones | 33db0b8718 | |
Tony Garnock-Jones | e923d87fa5 | |
Tony Garnock-Jones | 83697b0e56 | |
Tony Garnock-Jones | 1798e64615 | |
Tony Garnock-Jones | be32f9b7c8 | |
Tony Garnock-Jones | dc1b0ac54d | |
Tony Garnock-Jones | d579a0d607 | |
Tony Garnock-Jones | 7178fb0d9b | |
Tony Garnock-Jones | 4c0bd3b9d7 | |
Tony Garnock-Jones | b98f434ac9 | |
Tony Garnock-Jones | 61cec52d46 | |
Tony Garnock-Jones | f6ddf0ca3b | |
Tony Garnock-Jones | 9c7770a54f | |
Tony Garnock-Jones | cd29602761 | |
Tony Garnock-Jones | c411e47d7f | |
Tony Garnock-Jones | 897fc13054 | |
Tony Garnock-Jones | 9420cc7236 | |
Tony Garnock-Jones | e0ef236001 | |
Tony Garnock-Jones | 634b263ed2 | |
Tony Garnock-Jones | 2a6d0912b6 | |
Tony Garnock-Jones | 4a656dc929 | |
Tony Garnock-Jones | 2532b42959 | |
Tony Garnock-Jones | b12d49739c | |
Tony Garnock-Jones | aea735bb4e | |
Tony Garnock-Jones | 9b71388817 | |
Tony Garnock-Jones | b6ac046ba7 | |
Tony Garnock-Jones | 7b3731a5e4 | |
Tony Garnock-Jones | 185c233b2f | |
Tony Garnock-Jones | 22b2f162bc | |
Tony Garnock-Jones | f664399a8c | |
Tony Garnock-Jones | cd504becf7 | |
Tony Garnock-Jones | 48b2f06f8e | |
Tony Garnock-Jones | 284614eecb | |
Tony Garnock-Jones | 114875b52f | |
Tony Garnock-Jones | 12a690b4b5 | |
Tony Garnock-Jones | f01cbf7443 | |
Tony Garnock-Jones | 375cf291e0 | |
Tony Garnock-Jones | ab34971eef | |
Tony Garnock-Jones | 441941fb19 | |
Tony Garnock-Jones | 586385c716 | |
Tony Garnock-Jones | b192313c94 | |
Tony Garnock-Jones | 3153dc7c62 | |
Tony Garnock-Jones | 5edcca1e7f | |
Tony Garnock-Jones | 401e3973ee | |
Tony Garnock-Jones | b0001e44cb | |
Tony Garnock-Jones | 831f15099d | |
Tony Garnock-Jones | 782cbd73b2 | |
Tony Garnock-Jones | 6e3950cbc5 | |
Tony Garnock-Jones | cd4f8e410f | |
Tony Garnock-Jones | d540ee6faf | |
Tony Garnock-Jones | 0e43df1f9b | |
Tony Garnock-Jones | 694d4e7ae7 | |
Tony Garnock-Jones | c5bec4ea76 | |
Tony Garnock-Jones | 2e84614b3b | |
Tony Garnock-Jones | 071566b1e1 | |
Tony Garnock-Jones | 8a8facc080 | |
Tony Garnock-Jones | d4d1919957 | |
Tony Garnock-Jones | d0619cb164 | |
Tony Garnock-Jones | 85b3e513f9 | |
Tony Garnock-Jones | d638555239 | |
Tony Garnock-Jones | ebd8b3f05b | |
Tony Garnock-Jones | 39bfeedb54 | |
Tony Garnock-Jones | 2bfd5f4b03 | |
Tony Garnock-Jones | 3313674f15 | |
Tony Garnock-Jones | d11ec61714 | |
Tony Garnock-Jones | c89147dd6a | |
Tony Garnock-Jones | e0736e03c5 | |
Tony Garnock-Jones | d7b983e140 | |
Tony Garnock-Jones | 1a0772d39f | |
Tony Garnock-Jones | 9ae16b4561 | |
Tony Garnock-Jones | ae7555f6f3 | |
Tony Garnock-Jones | 63c124307b | |
Tony Garnock-Jones | 1784024952 | |
Tony Garnock-Jones | 9ed9296fc0 | |
Tony Garnock-Jones | 03cb5ab02f | |
Tony Garnock-Jones | 5820755277 | |
Tony Garnock-Jones | e269acebee | |
Tony Garnock-Jones | 334d9df81c | |
Tony Garnock-Jones | ac34d3fa8e | |
Tony Garnock-Jones | 000c0ff2be | |
Tony Garnock-Jones | e4fdd26c71 | |
Tony Garnock-Jones | 7fe4030dd5 | |
Tony Garnock-Jones | 61f298503e | |
Tony Garnock-Jones | 2eb12a30d6 | |
Tony Garnock-Jones | fb63ac24b0 | |
Tony Garnock-Jones | ec03bdb45f | |
Tony Garnock-Jones | 23e0e59daf | |
Tony Garnock-Jones | c18e9dd1fe | |
Tony Garnock-Jones | a69444f085 | |
Tony Garnock-Jones | 982d916b61 | |
Tony Garnock-Jones | 47b4c07268 | |
Tony Garnock-Jones | 8276a50552 | |
Tony Garnock-Jones | cf50e00f80 | |
Tony Garnock-Jones | c053102d07 | |
Tony Garnock-Jones | 1a2657fe33 | |
Tony Garnock-Jones | cdc5295a72 | |
Tony Garnock-Jones | 7c0fc8f358 | |
Tony Garnock-Jones | 1aedfe46b7 | |
Tony Garnock-Jones | e2e81a67c3 | |
Tony Garnock-Jones | 9d1db2d71f | |
Tony Garnock-Jones | eaa8963ead | |
Tony Garnock-Jones | 7958b9a6f2 | |
Tony Garnock-Jones | 8c9f3b13ee | |
Tony Garnock-Jones | b8fb7abab1 | |
Tony Garnock-Jones | 9595872177 | |
Tony Garnock-Jones | e8c0a2565e | |
Tony Garnock-Jones | 755a8bc73b | |
Tony Garnock-Jones | c30073c3f9 | |
Tony Garnock-Jones | a14b2d49b7 | |
Tony Garnock-Jones | 4869507b09 | |
Tony Garnock-Jones | c0df6c83a8 | |
Tony Garnock-Jones | 2445ab4a5a | |
Tony Garnock-Jones | 5b8c07cb3f | |
Tony Garnock-Jones | 55deeea343 | |
Tony Garnock-Jones | ee0f9a79fd | |
Tony Garnock-Jones | 49242bb4dc | |
Tony Garnock-Jones | 3893b0e581 | |
Tony Garnock-Jones | ed40b2198b | |
Tony Garnock-Jones | 9c90efe36b | |
Tony Garnock-Jones | e31f834d72 | |
Tony Garnock-Jones | af5de5b836 | |
Tony Garnock-Jones | 8db860648b | |
Tony Garnock-Jones | f009920dd7 | |
Tony Garnock-Jones | a2cb071f01 | |
Tony Garnock-Jones | 130e58a3e1 | |
Tony Garnock-Jones | 4b40bf174d | |
Tony Garnock-Jones | 01d3659216 | |
Tony Garnock-Jones | ccf277cddb | |
Tony Garnock-Jones | 4e471ed896 | |
Tony Garnock-Jones | 0eabbd4257 | |
Tony Garnock-Jones | 19a4edc36a | |
Tony Garnock-Jones | 975fc05d8f | |
Tony Garnock-Jones | 9cc537abf8 | |
Tony Garnock-Jones | 6a56dad886 | |
Tony Garnock-Jones | 7b4724f795 | |
Tony Garnock-Jones | 5b2db04a05 | |
Tony Garnock-Jones | 3253c665d6 | |
Tony Garnock-Jones | a123630bb5 | |
Tony Garnock-Jones | 04e1c1b0b1 | |
Tony Garnock-Jones | 71ead5abb7 | |
Tony Garnock-Jones | 9f0217b22a | |
Tony Garnock-Jones | 0f08461ccc | |
Tony Garnock-Jones | 53e15bc46c | |
Tony Garnock-Jones | 3360e013a4 | |
Tony Garnock-Jones | 01a766c974 | |
Tony Garnock-Jones | 5aee3af65a | |
Tony Garnock-Jones | 82f8bf1e0f | |
Tony Garnock-Jones | bc1f4f0ae5 | |
Tony Garnock-Jones | 82903af6c0 | |
Tony Garnock-Jones | 8ba216ff96 | |
Tony Garnock-Jones | e3d67af17f | |
Tony Garnock-Jones | 22e4d0b272 | |
Tony Garnock-Jones | c649339ab5 | |
Tony Garnock-Jones | 683ef11587 | |
Tony Garnock-Jones | 486528416d | |
Tony Garnock-Jones | 202d92d237 | |
Tony Garnock-Jones | 5d0bf91d30 | |
Tony Garnock-Jones | f47786871c | |
Tony Garnock-Jones | 3b89cbe880 | |
Tony Garnock-Jones | b7a2acf65b | |
Tony Garnock-Jones | 6cbfefc76d | |
Tony Garnock-Jones | 0cdcc1d8e7 | |
Tony Garnock-Jones | 98697e049d | |
Tony Garnock-Jones | 07b28ca042 | |
Tony Garnock-Jones | d11f008705 |
|
@ -1,4 +1,6 @@
|
|||
_site/
|
||||
cheatsheet.pdf
|
||||
preserves-expressions.pdf
|
||||
preserves-binary.pdf
|
||||
preserves-schema.pdf
|
||||
preserves-text.pdf
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg height="86" viewBox="0 0 76 86" width="76" xmlns="http://www.w3.org/2000/svg"><path d="m76 82v4h-76l.00080851-4zm-3-6v5h-70v-5zm-62.6696277-54 .8344146.4217275.4176066 6.7436084.4176065 10.9576581v10.5383496l-.4176065 13.1364492-.0694681 8.8498268-1.1825531.3523804h-4.17367003l-1.25202116-.3523804-.48627608-8.8498268-.41840503-13.0662957v-10.5375432l.41840503-11.028618.38167482-6.7798947.87034634-.3854412zm60.0004653 0 .8353798.4217275.4168913 6.7436084.4168913 10.9576581v10.5383496l-.4168913 13.1364492-.0686832 8.8498268-1.1835879.3523804h-4.1737047l-1.2522712-.3523804-.4879704-8.8498268-.4168913-13.0662957v-10.5375432l.4168913-11.028618.3833483-6.7798947.8697215-.3854412zm-42.000632 0 .8344979.4217275.4176483 6.7436084.4176482 10.9576581v10.5383496l-.4176482 13.1364492-.0686764 8.8498268-1.1834698.3523804h-4.1740866l-1.2529447-.3523804-.4863246-8.8498268-.4168497-13.0662957v-10.5375432l.4168497-11.028618.38331-6.7798947.8688361-.3854412zm23 0 .8344979.4217275.4176483 6.7436084.4176482 10.9576581v10.5383496l-.4176482 13.1364492-.0686764 8.8498268-1.1834698.3523804h-4.1740866l-1.2521462-.3523804-.4871231-8.8498268-.4168497-13.0662957v-10.5375432l.4168497-11.028618.38331-6.7798947.8696347-.3854412zm21.6697944-9v7h-70v-7zm-35.7200748-13 36.7200748 8.4088317-1.4720205 2.5911683h-70.32799254l-2.19998696-2.10140371z" fill="#2c2c2c" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
9
Makefile
9
Makefile
|
@ -1,6 +1,12 @@
|
|||
__ignored__ := $(shell ./setup.sh)
|
||||
|
||||
PDFS=preserves.pdf preserves-text.pdf preserves-binary.pdf preserves-schema.pdf
|
||||
PDFS=\
|
||||
preserves.pdf \
|
||||
preserves-text.pdf \
|
||||
preserves-binary.pdf \
|
||||
preserves-schema.pdf \
|
||||
preserves-expressions.pdf \
|
||||
cheatsheet.pdf
|
||||
|
||||
all: $(PDFS)
|
||||
|
||||
|
@ -16,4 +22,3 @@ test-all:
|
|||
(cd implementations/javascript; npm test)
|
||||
(cd implementations/python; make test)
|
||||
(cd implementations/racket/preserves; make testonly)
|
||||
(cd implementations/rust; cargo test)
|
||||
|
|
2
NOTICE
2
NOTICE
|
@ -1,2 +1,2 @@
|
|||
Preserves: an Expressive Data Language
|
||||
Copyright 2018-2022 Tony Garnock-Jones
|
||||
Copyright 2018-2024 Tony Garnock-Jones
|
||||
|
|
43
README.md
43
README.md
|
@ -7,7 +7,7 @@ no_site_title: true
|
|||
|
||||
This [repository]({{page.projectpages}}) contains a
|
||||
[definition](preserves.html) and various implementations of *Preserves*, a
|
||||
new data model, with associated serialization formats, in many ways
|
||||
data model with associated serialization formats in many ways
|
||||
comparable to JSON, XML, S-expressions, CBOR, ASN.1 BER, and so on.
|
||||
|
||||
## Core documents
|
||||
|
@ -24,6 +24,7 @@ automatic, perfect-fidelity conversion between syntaxes.
|
|||
- [Preserves textual syntax](preserves-text.html), and
|
||||
- [Preserves machine-oriented binary syntax](preserves-binary.html)
|
||||
- [Preserves tutorial](TUTORIAL.html)
|
||||
- [Quick Reference for Preserves syntax](cheatsheet.html)
|
||||
- [Canonical Form for Binary Syntax](canonical-binary.html)
|
||||
- [Syrup](https://github.com/ocapn/syrup#pseudo-specification), a
|
||||
hybrid binary/human-readable syntax for the Preserves data model
|
||||
|
@ -35,22 +36,30 @@ automatic, perfect-fidelity conversion between syntaxes.
|
|||
|
||||
## Implementations
|
||||
|
||||
Implementations of the data model, plus the textual and/or binary transfer syntaxes:
|
||||
#### Implementations of the data model, plus Preserves textual and binary transfer syntax
|
||||
|
||||
- [Preserves for Nim](https://git.syndicate-lang.org/ehmry/preserves-nim)
|
||||
- [Preserves for Python]({{page.projecttree}}/implementations/python/) ([`pip install preserves`](https://pypi.org/project/preserves/); [documentation available online](python/latest/))
|
||||
- [Preserves for Racket]({{page.projecttree}}/implementations/racket/preserves/) ([`raco pkg install preserves`](https://pkgs.racket-lang.org/package/preserves))
|
||||
- [Preserves for Rust]({{page.projecttree}}/implementations/rust/) ([crates.io package](https://crates.io/crates/preserves))
|
||||
- [Preserves for Squeak Smalltalk](https://squeaksource.com/Preserves.html) (`Installer ss project: 'Preserves'; install: 'Preserves'`)
|
||||
- [Preserves for TypeScript and JavaScript]({{page.projecttree}}/implementations/javascript/) ([`yarn add @preserves/core`](https://www.npmjs.com/package/@preserves/core))
|
||||
| Language[^pre-alpha-implementations] | Code | Package | Docs |
|
||||
|--------------------------------------|------------------------------------------------------------------------------|--------------------------------------------------------------------------------|-------------------------------------------|
|
||||
| Nim | [git.syndicate-lang.org](https://git.syndicate-lang.org/ehmry/preserves-nim) | | |
|
||||
| Python | [preserves.dev]({{page.projecttree}}/implementations/python/) | [`pip install preserves`](https://pypi.org/project/preserves/) | [docs](python/latest/) |
|
||||
| Racket | [preserves.dev]({{page.projecttree}}/implementations/racket/preserves/) | [`raco pkg install preserves`](https://pkgs.racket-lang.org/package/preserves) | |
|
||||
| Rust | [preserves.dev](https://gitlab.com/preserves/preserves-rs/) | [`cargo add preserves`](https://crates.io/crates/preserves) | [docs](https://docs.rs/preserves/latest/) |
|
||||
| Squeak Smalltalk | [SqueakSource](https://squeaksource.com/Preserves.html) | `Installer ss project: 'Preserves';`<br>` install: 'Preserves'` | |
|
||||
| TypeScript/JavaScript | [preserves.dev]({{page.projecttree}}/implementations/javascript/) | [`yarn add @preserves/core`](https://www.npmjs.com/package/@preserves/core) | |
|
||||
|
||||
Implementations of the data model, plus Syrup transfer syntax:
|
||||
[^pre-alpha-implementations]: Pre-alpha implementations also exist for
|
||||
[C]({{page.projecttree}}/implementations/c/) and
|
||||
[C++]({{page.projecttree}}/implementations/cpp/).
|
||||
|
||||
- [Syrup for Racket](https://github.com/ocapn/syrup/blob/master/impls/racket/syrup/syrup.rkt)
|
||||
- [Syrup for Guile](https://github.com/ocapn/syrup/blob/master/impls/guile/syrup.scm)
|
||||
- [Syrup for Python](https://github.com/ocapn/syrup/blob/master/impls/python/syrup.py)
|
||||
- [Syrup for JavaScript](https://github.com/zarutian/agoric-sdk/blob/zarutian/captp_variant/packages/captp/lib/syrup.js)
|
||||
- [Syrup for Haskell](https://github.com/zenhack/haskell-preserves)
|
||||
#### Implementations of the data model, plus Syrup transfer syntax
|
||||
|
||||
| Language | Code |
|
||||
|------------|----------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Guile | [github.com/ocapn/syrup](https://github.com/ocapn/syrup/blob/master/impls/guile/syrup.scm) |
|
||||
| Haskell | [github.com/zenhack/haskell-preserves](https://github.com/zenhack/haskell-preserves) |
|
||||
| JavaScript | [github.com/zarutian/agoric-sdk](https://github.com/zarutian/agoric-sdk/blob/zarutian/captp_variant/packages/captp/lib/syrup.js) |
|
||||
| Python | [github.com/ocapn/syrup](https://github.com/ocapn/syrup/blob/master/impls/python/syrup.py) |
|
||||
| Racket | [github.com/ocapn/syrup](https://github.com/ocapn/syrup/blob/master/impls/racket/syrup/syrup.rkt) |
|
||||
|
||||
## Tools
|
||||
|
||||
|
@ -64,7 +73,7 @@ Implementations of the data model, plus Syrup transfer syntax:
|
|||
|
||||
## Additional resources
|
||||
|
||||
- [Cheat sheet(s) for Preserves syntax](cheatsheet.html)
|
||||
- [Preserves Expressions (P-expressions, pexprs)](preserves-expressions.html)
|
||||
- Some [conventions for common data types](conventions.html)
|
||||
- [Open questions](questions.html); see also the
|
||||
[issues list]({{page.projectpages}}/issues)
|
||||
|
@ -79,4 +88,6 @@ Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|||
The contents of this repository are made available to you under the
|
||||
[Apache License, version 2.0](LICENSE)
|
||||
(<http://www.apache.org/licenses/LICENSE-2.0>), and are Copyright
|
||||
2018-2022 Tony Garnock-Jones.
|
||||
2018-2024 Tony Garnock-Jones.
|
||||
|
||||
## Notes
|
||||
|
|
61
TUTORIAL.md
61
TUTORIAL.md
|
@ -53,7 +53,7 @@ restricted to strings; more on this later, but for now, we will stick
|
|||
to the special comment annotation syntax.)
|
||||
|
||||
```
|
||||
;I'm an annotation... basically a comment. Ignore me!
|
||||
# I'm an annotation... basically a comment. Ignore me!
|
||||
"I'm data! Don't ignore me!"
|
||||
```
|
||||
|
||||
|
@ -61,23 +61,23 @@ Preserves supports some data types you're probably already familiar
|
|||
with from JSON, and which look fairly similar in the textual format:
|
||||
|
||||
```
|
||||
;booleans
|
||||
# booleans
|
||||
#t
|
||||
#f
|
||||
|
||||
;various kinds of numbers:
|
||||
# various kinds of numbers:
|
||||
42
|
||||
123556789012345678901234567890
|
||||
-10
|
||||
13.5
|
||||
|
||||
;strings
|
||||
# strings
|
||||
"I'm feeling stringy!"
|
||||
|
||||
;sequences (lists)
|
||||
# sequences (lists)
|
||||
["cat", "dog", "mouse", "goldfish"]
|
||||
|
||||
;dictionaries (hashmaps)
|
||||
# dictionaries (hashmaps)
|
||||
{"cat": "meow",
|
||||
"dog": "woof",
|
||||
"goldfish": "glub glub",
|
||||
|
@ -95,29 +95,26 @@ bit different.
|
|||
A few more interesting differences:
|
||||
|
||||
```
|
||||
;Preserves treats commas as whitespace, so these are the same
|
||||
# Preserves treats commas as whitespace, so these are the same
|
||||
["cat", "dog", "mouse", "goldfish"]
|
||||
["cat" "dog" "mouse" "goldfish"]
|
||||
|
||||
;We can use anything as keys in dictionaries, not just strings
|
||||
# We can use anything as keys in dictionaries, not just strings
|
||||
{1: "the loneliest number",
|
||||
["why", "was", 6, "afraid", "of", 7]: "because 7 8 9",
|
||||
{"dictionaries": "as keys???"}: "well, why not?"}
|
||||
```
|
||||
|
||||
Preserves technically provides a few types of numbers:
|
||||
Preserves technically provides various types of numbers:
|
||||
|
||||
```
|
||||
;Signed Integers
|
||||
# Signed Integers
|
||||
42
|
||||
-42
|
||||
5907212309572059846509324862304968273468909473609826340
|
||||
-5907212309572059846509324862304968273468909473609826340
|
||||
|
||||
;Floats (Single-precision IEEE floats) (notice the trailing f)
|
||||
3.1415927f
|
||||
|
||||
;Doubles (Double-precision IEEE floats)
|
||||
# Doubles (Double-precision IEEE floats)
|
||||
3.141592653589793
|
||||
```
|
||||
|
||||
|
@ -130,31 +127,31 @@ to the program, but not textual importance (other than to guide the
|
|||
programmer… not unlike variable names).
|
||||
|
||||
```
|
||||
;A symbol (NOT a string!)
|
||||
# A symbol (NOT a string!)
|
||||
JustASymbol
|
||||
|
||||
;You can do mixedCase or CamelCase too of course, pick your poison
|
||||
;(but be consistent, for the sake of your collaborators!)
|
||||
# You can do mixedCase or CamelCase too of course, pick your poison
|
||||
# (but be consistent, for the sake of your collaborators!)
|
||||
iAmASymbol
|
||||
i-am-a-symbol
|
||||
|
||||
;A list of symbols
|
||||
# A list of symbols
|
||||
[GET, PUT, POST, DELETE]
|
||||
|
||||
;A symbol with spaces in it
|
||||
# A symbol with spaces in it
|
||||
|this is just one symbol believe it or not|
|
||||
```
|
||||
|
||||
We can also add binary data, aka ByteStrings:
|
||||
|
||||
```
|
||||
;Some binary data, base64 encoded
|
||||
# Some binary data, base64 encoded
|
||||
#[cGljdHVyZSBvZiBhIGNhdA==]
|
||||
|
||||
;Some other binary data, hexadecimal encoded
|
||||
# Some other binary data, hexadecimal encoded
|
||||
#x"616263"
|
||||
|
||||
;Same binary data as above, base64 encoded
|
||||
# Same binary data as above, base64 encoded
|
||||
#[YWJj]
|
||||
```
|
||||
|
||||
|
@ -186,14 +183,14 @@ useful for many contexts, but especially for cryptographic signatures
|
|||
and hashing.
|
||||
|
||||
```
|
||||
;This hand-typed Preserves document...
|
||||
# This hand-typed Preserves document...
|
||||
{monkey: {"noise": "ooh-ooh",
|
||||
"eats": #{"bananas", "berries"}}
|
||||
cat: {"noise": "meow",
|
||||
"eats": #{"kibble", "cat treats", "tinned meat"}}}
|
||||
|
||||
;Will always, always be written out in this order (except in
|
||||
;binary, of course) when canonicalized:
|
||||
# Will always, always be written out in this order (except in
|
||||
# binary, of course) when canonicalized:
|
||||
{cat: {"eats": #{"cat treats", "kibble", "tinned meat"},
|
||||
"noise": "meow"}
|
||||
monkey: {"eats": #{"bananas", "berries"},
|
||||
|
@ -237,12 +234,12 @@ This causes a problem.
|
|||
Now we might have two kinds of entries:
|
||||
|
||||
```
|
||||
;Exact date known
|
||||
# Exact date known
|
||||
{"name": "Gregor Samsa",
|
||||
"description": "humanoid trapped in an insect body",
|
||||
"born": "1915-10-04"}
|
||||
|
||||
;Not sure about exact date...
|
||||
# Not sure about exact date...
|
||||
{"name": "Gregor Samsa",
|
||||
"description": "humanoid trapped in an insect body",
|
||||
"born": "Sometime in October 1915? Or was that when he became an insect?"}
|
||||
|
@ -255,12 +252,12 @@ edge cases.
|
|||
No, it's better to be able to have a separate type:
|
||||
|
||||
```
|
||||
;Exact date known
|
||||
# Exact date known
|
||||
{"name": "Gregor Samsa",
|
||||
"description": "humanoid trapped in an insect body",
|
||||
"born": <Date 1915 10 04>}
|
||||
|
||||
;Not sure about exact date...
|
||||
# Not sure about exact date...
|
||||
{"name": "Gregor Samsa",
|
||||
"description": "humanoid trapped in an insect body",
|
||||
"born": <Unknown "Sometime in October 1915? Or was that when he became an insect?">}
|
||||
|
@ -321,17 +318,17 @@ in some circumstances.
|
|||
We have previously shown them used as comments:
|
||||
|
||||
```
|
||||
;I'm a comment!
|
||||
# I'm a comment!
|
||||
"I am not a comment, I am data!"
|
||||
```
|
||||
|
||||
Annotations annotate the values they precede.
|
||||
It is possible to have multiple annotations on a value.
|
||||
The `;`-based comment syntax is syntactic sugar for the general
|
||||
The hash-space (or hash-tab) comment syntax is syntactic sugar for the general
|
||||
`@`-prefixed string annotation syntax.
|
||||
|
||||
```
|
||||
;I am annotating this number
|
||||
# I am annotating this number
|
||||
@"And so am I!"
|
||||
42
|
||||
```
|
||||
|
|
|
@ -13,5 +13,5 @@ defaults:
|
|||
layout: page
|
||||
|
||||
title: "Preserves"
|
||||
version_date: "October 2023"
|
||||
version: "0.7.1"
|
||||
version_date: "March 2024"
|
||||
version: "0.995.0"
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
[
|
||||
{"version":"0.18.1","title":"0.18.1","aliases":["latest"]},
|
||||
{"version":"0.995.1","title":"0.995.1","aliases":["latest"]},
|
||||
{"version":"0.995.0","title":"0.995.0","aliases":[]},
|
||||
{"version":"0.994.0","title":"0.994.0","aliases":[]},
|
||||
{"version":"0.993.0","title":"0.993.0","aliases":[]},
|
||||
{"version":"0.992.2","title":"0.992.2","aliases":[]},
|
||||
{"version":"0.992.1","title":"0.992.1","aliases":[]},
|
||||
{"version":"0.992.0","title":"0.992.0","aliases":[]},
|
||||
{"version":"0.991.0","title":"0.991.0","aliases":[]},
|
||||
{"version":"0.990.1","title":"0.990.1","aliases":[]},
|
||||
{"version":"0.990.0","title":"0.990.0","aliases":[]},
|
||||
{"version":"0.18.1","title":"0.18.1","aliases":[]},
|
||||
{"version":"0.18.0","title":"0.18.0","aliases":[]}
|
||||
]
|
|
@ -0,0 +1,31 @@
|
|||
For a value `V`, we write `«V»` for the binary encoding of `V`.
|
||||
|
||||
```text
|
||||
«#f» = [0x80]
|
||||
«#t» = [0x81]
|
||||
|
||||
«@W V» = [0x85] ++ «W» ++ «V»
|
||||
«#:V» = [0x86] ++ «V»
|
||||
|
||||
«V» if V ∈ Double = [0x87, 0x08] ++ binary64(V)
|
||||
|
||||
«V» if V ∈ SignedInteger = [0xB0] ++ varint(|intbytes(V)|) ++ intbytes(V)
|
||||
«V» if V ∈ String = [0xB1] ++ varint(|utf8(V)|) ++ utf8(V)
|
||||
«V» if V ∈ ByteString = [0xB2] ++ varint(|V|) ++ V
|
||||
«V» if V ∈ Symbol = [0xB3] ++ varint(|utf8(V)|) ++ utf8(V)
|
||||
|
||||
«<L F_1...F_m>» = [0xB4] ++ «L» ++ «F_1» ++...++ «F_m» ++ [0x84]
|
||||
«[X_1...X_m]» = [0xB5] ++ «X_1» ++...++ «X_m» ++ [0x84]
|
||||
«#{E_1...E_m}» = [0xB6] ++ «E_1» ++...++ «E_m» ++ [0x84]
|
||||
«{K_1:V_1...K_m:V_m}» = [0xB7] ++ «K_1» ++ «V_1» ++...++ «K_m» ++ «V_m» ++ [0x84]
|
||||
|
||||
varint(n) = [n] if n < 128
|
||||
[(n & 127) | 128] ++ varint(n >> 7) if n ≥ 128
|
||||
|
||||
intbytes(n) = the empty sequence if n = 0, otherwise signedBigEndian(n)
|
||||
|
||||
signedBigEndian(n) = [n & 255] if -128 ≤ n ≤ 127
|
||||
signedBigEndian(n >> 8) ++ [n & 255] otherwise
|
||||
```
|
||||
|
||||
The function `binary64(D)` yields the big-endian 8-byte IEEE 754 binary representation of `D`.
|
|
@ -1 +1,51 @@
|
|||
(TODO)
|
||||
For a value <span class="postcard-grammar binarysyntax">*V*</span>, we write <span
|
||||
class="postcard-grammar binarysyntax">«*V*»</span> for the binary encoding of <span
|
||||
class="postcard-grammar binarysyntax">*V*</span>.
|
||||
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
«`#f`» | = | `80`
|
||||
«`#t`» | = | `81`
|
||||
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
«`@`*W* *V*» | = | `85` «*W*» «*V*»
|
||||
«`#:`*V*» | = | `86` «*V*»
|
||||
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
«*V*» | = | `87``08` **binary64**(*V*) | if *V* ∈ Double
|
||||
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
«*V*» | = | `B0` **varint**(|**intbytes**(*V*)|) **intbytes**(*V*) | if *V* ∈ SignedInteger
|
||||
«*V*» | = | `B1` **varint**(|**utf8**(*V*)|) **utf8**(*V*) | if *V* ∈ String
|
||||
«*V*» | = | `B2` **varint**(|*V*|) *V* | if *V* ∈ ByteString
|
||||
«*V*» | = | `B3` **varint**(|**utf8**(*V*)|) **utf8**(*V*) | if *V* ∈ Symbol
|
||||
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
«`<`*L* *F*<sub>1</sub> ... *F*<sub>m</sub>`>`» | = | `B4` «*L*» «*F*<sub>1</sub>» ... «*F*<sub>m</sub>» `84`
|
||||
«`[`*X*<sub>1</sub> ... *X*<sub>m</sub>`]`» | = | `B5` «*X*<sub>1</sub>» ... «*X*<sub>m</sub>» `84`
|
||||
«`#{`*E*<sub>1</sub> ... *E*<sub>m</sub>`}`» | = | `B6` «*E*<sub>1</sub>» ... «*E*<sub>m</sub>» `84`
|
||||
«`{`*K*<sub>1</sub>`:`*V*<sub>1</sub> ... *K*<sub>m</sub>`:`*V*<sub>m</sub>`}`» | = | `B7` «*K*<sub>1</sub>» «*V*<sub>1</sub>» ... «*K*<sub>m</sub>» «*V*<sub>m</sub>» `84`
|
||||
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
**varint**(*n*) | = | <span class="outputish">*n*</span> | if *n* < 128
|
||||
| | <span class="outputish">(*n* & 127) | 128</span> **varint**(*n* >> 7) | if *n* ≥ 128
|
||||
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
**intbytes**(*n*) | = | <span class="roman">the empty sequence if</span> *n* = 0<span class="roman">, otherwise</span> **signedBigEndian**(*n*)
|
||||
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
**signedBigEndian**(*n*) | = | <span class="outputish">*n* & 255</span> | if −128 ≤ *n* ≤ 127
|
||||
| | **signedBigEndian**(*n* >> 8) <span class="outputish">*n* & 255</span> | otherwise
|
||||
|
||||
The function <span class="postcard-grammar binarysyntax">**binary64**(*D*)</span> yields the
|
||||
big-endian 8-byte IEEE 754 binary representation of <span class="postcard-grammar
|
||||
binarysyntax">*D*</span>.
|
||||
|
||||
<!--
|
||||
Together, <span class="postcard-grammar binarysyntax">**div**</span> and <span
|
||||
class="postcard-grammar binarysyntax">**mod**</span> give [Euclidean
|
||||
division](https://en.wikipedia.org/wiki/Euclidean_division); that is, if
|
||||
<span class="postcard-grammar binarysyntax">*n* **div** *d* = *q*</span> and
|
||||
<span class="postcard-grammar binarysyntax">*n* **mod** *d* = *r*</span>, then
|
||||
<span class="postcard-grammar binarysyntax">*n* = *dq* + *r*</span> and
|
||||
<span class="postcard-grammar binarysyntax">0 ≤ *r* < |d|</span>.
|
||||
-->
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
The definitions of `Atom`, `ws`, and `linecomment` are as given in the Preserves text syntax.
|
||||
|
||||
```text
|
||||
Document := Expr* Trailer ws
|
||||
Expr := ws (SimpleExpr | Punct)
|
||||
SimpleExpr := Compound | Embedded | Annotated | Atom
|
||||
Compound := Sequence | Record | Block | Group | Set
|
||||
Punct := `,` | `;` | `:`+
|
||||
|
||||
Sequence := `[` Expr* Trailer ws `]`
|
||||
Record := `<` Expr* Trailer ws `>`
|
||||
Block := `{` Expr* Trailer ws `}`
|
||||
Group := `(` Expr* Trailer ws `)`
|
||||
Set := `#{` Expr* Trailer ws `}`
|
||||
|
||||
Trailer := (ws Annotation)*
|
||||
|
||||
Embedded := `#:` SimpleExpr
|
||||
Annotated := Annotation SimpleExpr
|
||||
Annotation := `@` SimpleExpr | `#` ((space | tab | `!`) linecomment) (cr | lf)
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
The definitions of `Atom`, `ws`, and `linecomment` are as given in the Preserves text syntax.
|
||||
|
||||
{:.postcard-grammar.textsyntax}
|
||||
| *Document* | := | *Expr*<sup>⋆</sup> *Trailer* **ws**
|
||||
| *Expr* | := | **ws** (*SimpleExpr* | *Punct*)
|
||||
| *SimpleExpr* | := | *Compound* | *Embedded* | *Annotated* | *Atom*
|
||||
| *Compound* | := | *Sequence* | *Record* | *Block* | *Group* | *Set*
|
||||
| *Punct* | := | `,` | `;` | `:`<sup>+</sup>
|
||||
|
||||
{:.postcard-grammar.textsyntax}
|
||||
| *Sequence* | := | `[` *Expr*<sup>⋆</sup> *Trailer* **ws** `]`
|
||||
| *Record* | := | `<` *Expr*<sup>⋆</sup> *Trailer* **ws** `>`
|
||||
| *Block* | := | `{` *Expr*<sup>⋆</sup> *Trailer* **ws** `}`
|
||||
| *Group* | := | `(` *Expr*<sup>⋆</sup> *Trailer* **ws** `)`
|
||||
| *Set* | := | `#{` *Expr*<sup>⋆</sup> *Trailer* **ws** `}`
|
||||
|
||||
{:.postcard-grammar.textsyntax}
|
||||
| *Trailer* | := | (**ws** *Annotation*)<sup>⋆</sup>
|
||||
|
||||
{:.postcard-grammar.textsyntax}
|
||||
| *Embedded* | := | `#:` *SimpleExpr*
|
||||
| *Annotated* | := | *Annotation* *SimpleExpr*
|
||||
| *Annotation* | := | `@` *SimpleExpr* | `#` ((**space** | **tab** | `!`) *linecomment*) (**cr** | **lf**)
|
|
@ -0,0 +1,48 @@
|
|||
```text
|
||||
Document := Value ws
|
||||
Value := ws (Record | Collection | Embedded | Annotated | Atom)
|
||||
Collection := Sequence | Dictionary | Set
|
||||
|
||||
Record := `<` Value+ ws `>`
|
||||
Sequence := `[` (commas Value)* commas `]`
|
||||
Set := `#{` (commas Value)* commas `}`
|
||||
Dictionary := `{` (commas Value ws `:` Value)* commas `}`
|
||||
commas := (ws `,`)* ws
|
||||
|
||||
Embedded := `#:` Value
|
||||
Annotated := Annotation Value
|
||||
Annotation := `@` Value | `#` ((space | tab | `!`) linecomment) (cr | lf)
|
||||
|
||||
Atom := Boolean | ByteString | String | QuotedSymbol | Symbol | Number
|
||||
Boolean := `#t` | `#f`
|
||||
ByteString := `#"` binchar* `"`
|
||||
| `#x"` (ws hex hex)* ws `"`
|
||||
| `#[` (ws base64char)* ws `]`
|
||||
String := `"` («any unicode scalar except `\` or `"`» | escaped | `\"`)* `"`
|
||||
QuotedSymbol := `|` («any unicode scalar except `\` or `|`» | escaped | `\|`)* `|`
|
||||
Symbol := (`A`..`Z` | `a`..`z` | `0`..`9` | sympunct | symuchar)+
|
||||
Number := Double | SignedInteger
|
||||
Double := flt | `#xd"` (ws hex hex)8 ws `"`
|
||||
SignedInteger := int
|
||||
|
||||
escaped := `\\` | `\/` | `\b` | `\f` | `\n` | `\r` | `\t` | `\u` hex hex hex hex
|
||||
binescaped := `\\` | `\/` | `\b` | `\f` | `\n` | `\r` | `\t` | `\x` hex hex
|
||||
binchar := «any scalar ≥32 and ≤126, except `\` or `"`» | binescaped | `\"`
|
||||
base64char := `A`..`Z` | `a`..`z` | `0`..`9` | `+` | `/` | `-` | `_` | `=`
|
||||
sympunct := `~` | `!` | `$` | `%` | `^` | `&` | `*` | `?`
|
||||
| `_` | `=` | `+` | `-` | `/` | `.`
|
||||
symuchar := «any scalar value ≥128 whose Unicode category is
|
||||
Lu, Ll, Lt, Lm, Lo, Mn, Mc, Me, Nd, Nl, No, Pc,
|
||||
Pd, Po, Sc, Sm, Sk, So, or Co»
|
||||
|
||||
flt := int ( frac exp | frac | exp )
|
||||
int := (`-`|`+`) (`0`..`9`)+
|
||||
frac := `.` (`0`..`9`)+
|
||||
exp := (`e`|`E`) (`-`|`+`) (`0`..`9`)+
|
||||
hex := `A`..`F` | `a`..`f` | `0`..`9`
|
||||
|
||||
ws := (space | tab | cr | lf)*
|
||||
delimiter := ws | `<` | `>` | `[` | `]` | `{` | `}`
|
||||
| `#` | `:` | `"` | `|` | `@` | `;` | `,`
|
||||
linecomment := «any unicode scalar except cr or lf»*
|
||||
```
|
|
@ -0,0 +1,47 @@
|
|||
{:.postcard-grammar.textsyntax}
|
||||
| *Document* | := | *Value* **ws** |
|
||||
| *Value* | := | **ws** (*Record* | *Collection* | *Embedded* | *Annotated* | *Atom*) |
|
||||
| *Collection* | := | *Sequence* | *Dictionary* | *Set* |
|
||||
|
||||
{:.postcard-grammar.textsyntax}
|
||||
| *Record* | := | `<`*Value*<sup>+</sup> **ws**`>` |
|
||||
| *Sequence* | := | `[`(**commas** *Value*)<sup>⋆</sup> **commas**`]` |
|
||||
| *Set* | := | `#{`(**commas** *Value*)<sup>⋆</sup> **commas**`}` |
|
||||
| *Dictionary* | := | `{` (**commas** *Value* **ws**`:`*Value*)<sup>⋆</sup> **commas**`}` |
|
||||
| **commas** | := | (**ws** `,`)<sup>⋆</sup> **ws** |
|
||||
|
||||
{:.postcard-grammar.textsyntax}
|
||||
| *Embedded* | := | `#:`*Value* |
|
||||
| *Annotated* | := | *Annotation* *Value* |
|
||||
| *Annotation* | := | `@`*Value* |`#` ((**space** | **tab** | `!`) *linecomment*) (**cr** | **lf**) |
|
||||
|
||||
{:.postcard-grammar.textsyntax}
|
||||
| *Atom* | := | *Boolean* | *ByteString* | *String* | *QuotedSymbol* | *Symbol* | *Number* |
|
||||
| *Boolean* | := | `#t`|`#f` |
|
||||
| *ByteString* | := | `#"`*binchar*<sup>⋆</sup> `"`|`#x"` (**ws** *hex* *hex*)<sup>⋆</sup> **ws**`"`|`#[` (**ws** *base64char*)<sup>⋆</sup> **ws**`]` |
|
||||
| *String* | := | `"` (« any unicode scalar value except `\` or `"` » | *escaped* |`\"`)<sup>⋆</sup> `"` |
|
||||
| *QuotedSymbol* | := | `|` (« any unicode scalar value except `\` or `|` » | *escaped* |`\|`)<sup>⋆</sup> `|` |
|
||||
| *Symbol* | := | (`A`..`Z`|`a`..`z`|`0`..`9`| *sympunct* | *symuchar*)<sup>+</sup> |
|
||||
| *Number* | := | *Double* | *SignedInteger* |
|
||||
| *Double* | := | *flt* |`#xd"` (**ws** *hex* *hex*)<sup>8</sup> **ws**`"` |
|
||||
| *SignedInteger* | := | *int* |
|
||||
|
||||
{:.postcard-grammar.textsyntax}
|
||||
| *escaped* | := | `\\`|`\/`|`\b`|`\f`|`\n`|`\r`|`\t`|`\u`*hex* *hex* *hex* *hex* |
|
||||
| *binescaped* | := | `\\`|`\/`|`\b`|`\f`|`\n`|`\r`|`\t`|`\x`*hex* *hex* |
|
||||
| *binchar* | := | « any unicode scalar value ≥32 and ≤126, except `\` or `"` » | *binescaped* |`\"` |
|
||||
| *base64char* | := | `A`..`Z`|`a`..`z`|`0`..`9`|`+`|`/`|`-`|`_`|`=` |
|
||||
| *sympunct* | := | `~`|`!`|`$`|`%`|`^`|`&`|`*`|`?`|`_`|`=`|`+`|`-`|`/`|`.` |
|
||||
| *symuchar* | := | « any scalar value ≥128 whose Unicode category is Lu, Ll, Lt, Lm, Lo, Mn, Mc, Me, Nd, Nl, No, Pc, Pd, Po, Sc, Sm, Sk, So, or Co » |
|
||||
|
||||
{:.postcard-grammar.textsyntax}
|
||||
| *flt* | := | *int* ( *frac* *exp* | *frac* | *exp* ) |
|
||||
| *int* | := | (`-`|`+`) (`0`..`9`)<sup>+</sup> |
|
||||
| *frac* | := | `.` (`0`..`9`)<sup>+</sup> |
|
||||
| *exp* | := | (`e`|`E`) (`-`|`+`) (`0`..`9`)<sup>+</sup> |
|
||||
| *hex* | := | `A`..`F`|`a`..`f`|`0`..`9` |
|
||||
|
||||
{:.postcard-grammar.textsyntax}
|
||||
| **ws** | := | (**space** | **tab** | **cr** | **lf**)<sup>⋆</sup> |
|
||||
| **delimiter** | := | **ws** | `<` | `>` | `[` | `]` | `{` | `}` | `#` | `:` | `"` | `|` | `@` | `;` | `,` |
|
||||
| *linecomment* | := | « any unicode scalar value except **cr** or **lf** »<sup>⋆</sup> |
|
|
@ -0,0 +1 @@
|
|||
<svg height="86" viewBox="0 0 76 86" height="12" xmlns="http://www.w3.org/2000/svg"><path d="m76 82v4h-76l.00080851-4zm-3-6v5h-70v-5zm-62.6696277-54 .8344146.4217275.4176066 6.7436084.4176065 10.9576581v10.5383496l-.4176065 13.1364492-.0694681 8.8498268-1.1825531.3523804h-4.17367003l-1.25202116-.3523804-.48627608-8.8498268-.41840503-13.0662957v-10.5375432l.41840503-11.028618.38167482-6.7798947.87034634-.3854412zm60.0004653 0 .8353798.4217275.4168913 6.7436084.4168913 10.9576581v10.5383496l-.4168913 13.1364492-.0686832 8.8498268-1.1835879.3523804h-4.1737047l-1.2522712-.3523804-.4879704-8.8498268-.4168913-13.0662957v-10.5375432l.4168913-11.028618.3833483-6.7798947.8697215-.3854412zm-42.000632 0 .8344979.4217275.4176483 6.7436084.4176482 10.9576581v10.5383496l-.4176482 13.1364492-.0686764 8.8498268-1.1834698.3523804h-4.1740866l-1.2529447-.3523804-.4863246-8.8498268-.4168497-13.0662957v-10.5375432l.4168497-11.028618.38331-6.7798947.8688361-.3854412zm23 0 .8344979.4217275.4176483 6.7436084.4176482 10.9576581v10.5383496l-.4176482 13.1364492-.0686764 8.8498268-1.1834698.3523804h-4.1740866l-1.2521462-.3523804-.4871231-8.8498268-.4168497-13.0662957v-10.5375432l.4168497-11.028618.38331-6.7798947.8696347-.3854412zm21.6697944-9v7h-70v-7zm-35.7200748-13 36.7200748 8.4088317-1.4720205 2.5911683h-70.32799254l-2.19998696-2.10140371z" fill="currentColor" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,129 @@
|
|||
#lang racket
|
||||
|
||||
(define (base-bytes v)
|
||||
(define byte-count (cond [(zero? v) 0]
|
||||
[else (define raw-bit-count (+ (integer-length v) 1))
|
||||
(quotient (+ raw-bit-count 7) 8)]))
|
||||
(for/list [(shift (in-range (* byte-count 8) 0 -8))]
|
||||
(bitwise-bit-field v (- shift 8) shift)))
|
||||
|
||||
(define (mod n d) (modulo n d)) ;; sign equal to sign of d
|
||||
(define (div n d) (/ (- n (mod n d)) d)) ;; sign equal to sign of n, or zero
|
||||
|
||||
;;---------------------------------------------------------------------------
|
||||
;; One
|
||||
;;
|
||||
;; (define (intbytes* v)
|
||||
;; (cond [(zero? v) '()]
|
||||
;; [(= v -1) '()]
|
||||
;; [else (append (intbytes* (div v 256)) (list (mod v 256)))]))
|
||||
;;
|
||||
;; (define (intbytes v)
|
||||
;; (define bs (intbytes* v))
|
||||
;; (define looks-negative (and (pair? bs) (>= (car bs) 128)))
|
||||
;; (if (xor (negative? v) looks-negative)
|
||||
;; (cons (if (negative? v) 255 0) bs)
|
||||
;; bs))
|
||||
|
||||
;;---------------------------------------------------------------------------
|
||||
;; Two
|
||||
;;
|
||||
;; (define (intbytes** v)
|
||||
;; (cond [(zero? v) '()]
|
||||
;; [else (append (intbytes** (quotient v 256)) (list (remainder v 256)))]))
|
||||
;;
|
||||
;; (define (intbytes* v)
|
||||
;; (define bs (intbytes** v))
|
||||
;; (if (and (pair? bs) (>= (car bs) 128))
|
||||
;; (cons 0 bs)
|
||||
;; bs))
|
||||
;;
|
||||
;; (define (intbytes v)
|
||||
;; (if (negative? v)
|
||||
;; (map (lambda (n) (- 255 n)) (intbytes* (- (+ v 1))))
|
||||
;; (intbytes* v)))
|
||||
|
||||
;;---------------------------------------------------------------------------
|
||||
;; Three
|
||||
;;
|
||||
;; (define (intbytes+ v)
|
||||
;; (cond [(>= v 128) (append (intbytes+ (div v 256)) (list (mod v 256)))]
|
||||
;; [else (list (mod v 256))]))
|
||||
;;
|
||||
;; (define (intbytes- v)
|
||||
;; (cond [(< v -128) (append (intbytes- (div v 256)) (list (mod v 256)))]
|
||||
;; [else (list (mod v 256))]))
|
||||
;;
|
||||
;; (define (intbytes v)
|
||||
;; (cond [(negative? v) (intbytes- v)]
|
||||
;; [(zero? v) '()]
|
||||
;; [(positive? v) (intbytes+ v)]))
|
||||
|
||||
;;---------------------------------------------------------------------------
|
||||
;; Four
|
||||
;;
|
||||
;; (define (intbytes* v)
|
||||
;; (append (if (<= -128 v 127)
|
||||
;; '()
|
||||
;; (intbytes* (div v 256)))
|
||||
;; (list (mod v 256))))
|
||||
;;
|
||||
;; (define (intbytes v)
|
||||
;; (if (zero? v)
|
||||
;; '()
|
||||
;; (intbytes* v)))
|
||||
|
||||
;;---------------------------------------------------------------------------
|
||||
;; Five
|
||||
;;
|
||||
;; (define (intbytes* v)
|
||||
;; (if (<= -128 v 127)
|
||||
;; (list (mod v 256))
|
||||
;; (append (intbytes* (div v 256)) (list (mod v 256)))))
|
||||
;;
|
||||
;; (define (intbytes v)
|
||||
;; (if (zero? v)
|
||||
;; '()
|
||||
;; (intbytes* v)))
|
||||
|
||||
;;---------------------------------------------------------------------------
|
||||
;; Six
|
||||
|
||||
(define (intbytes* v)
|
||||
(if (<= -128 v 127)
|
||||
(list (bitwise-and v 255))
|
||||
(append (intbytes* (arithmetic-shift v -8)) (list (bitwise-and v 255)))))
|
||||
|
||||
(define (intbytes v)
|
||||
(if (zero? v)
|
||||
'()
|
||||
(intbytes* v)))
|
||||
|
||||
(define cases `(
|
||||
(-257 (#xfe #xff))
|
||||
(-256 (#xff #x00))
|
||||
(-255 (#xff #x01))
|
||||
(-129 (#xff #x7f))
|
||||
(-128 (#x80))
|
||||
(-127 (#x81))
|
||||
(-2 (#xfe))
|
||||
(-1 (#xff))
|
||||
(0 ())
|
||||
(1 (#x01))
|
||||
(127 (#x7f))
|
||||
(128 (#x00 #x80))
|
||||
(255 (#x00 #xff))
|
||||
(256 (#x01 #x00))
|
||||
(32767 (#x7f #xff))
|
||||
(32768 (#x00 #x80 #x00))
|
||||
(65535 (#x00 #xff #xff))
|
||||
(65536 (#x01 #x00 #x00))
|
||||
))
|
||||
|
||||
(module+ test
|
||||
(require rackunit)
|
||||
(for [(c (in-list cases))]
|
||||
(match-define (list input output) c)
|
||||
(writeln (list input output (base-bytes input) (intbytes input)))
|
||||
(check-equal? output (base-bytes input))
|
||||
(check-equal? output (intbytes input))))
|
|
@ -1,5 +1,5 @@
|
|||
Python's strings, byte strings, integers, booleans, and double-precision floats stand directly
|
||||
for their Preserves counterparts. Wrapper objects for [Float][preserves.values.Float] and
|
||||
for their Preserves counterparts. Wrapper objects for
|
||||
[Symbol][preserves.values.Symbol] complete the suite of atomic types.
|
||||
|
||||
Python's lists and tuples correspond to Preserves `Sequence`s, and dicts and sets to
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
Here are a few example values, written using the [text
|
||||
syntax](https://preserves.dev/preserves-text.html):
|
||||
|
||||
Boolean : #t #f
|
||||
Double : 1.0 10.4e3 -100.6
|
||||
Integer : 1 0 -100
|
||||
String : "Hello, world!\n"
|
||||
ByteString : #"bin\x00str\x00" #[YmluAHN0cgA] #x"62696e0073747200"
|
||||
Symbol : hello-world |hello world| = ! hello? || ...
|
||||
Record : <label field1 field2 ...>
|
||||
Sequence : [value1 value2 ...]
|
||||
Set : #{value1 value2 ...}
|
||||
Dictionary : {key1: value1 key2: value2 ...: ...}
|
||||
Embedded : #:value
|
||||
|
||||
Commas are optional in sequences, sets, and dictionaries.
|
|
@ -1,17 +1,17 @@
|
|||
Value = Atom
|
||||
| Compound
|
||||
| Embedded
|
||||
```text
|
||||
Value = Atom
|
||||
| Compound
|
||||
| Embedded
|
||||
|
||||
Atom = Boolean
|
||||
| Float
|
||||
| Double
|
||||
| SignedInteger
|
||||
| String
|
||||
| ByteString
|
||||
| Symbol
|
||||
|
||||
Compound = Record
|
||||
| Sequence
|
||||
| Set
|
||||
| Dictionary
|
||||
Atom = Boolean
|
||||
| Double
|
||||
| SignedInteger
|
||||
| String
|
||||
| ByteString
|
||||
| Symbol
|
||||
|
||||
Compound = Record
|
||||
| Sequence
|
||||
| Set
|
||||
| Dictionary
|
||||
```
|
||||
|
|
|
@ -17,6 +17,7 @@ extra_html_headers: >
|
|||
<div class="middle">
|
||||
<ul>
|
||||
<li><a href="{{site.baseurl}}/preserves.html">Core</a>
|
||||
<li><a href="{{site.baseurl}}/cheatsheet.html">QuickRef</a>
|
||||
<li><a href="{{site.baseurl}}/preserves-schema.html">Schema</a>
|
||||
<li><a href="{{site.baseurl}}/preserves-path.html">Path</a>
|
||||
</ul>
|
||||
|
@ -24,7 +25,7 @@ extra_html_headers: >
|
|||
<div class="right">
|
||||
<a href="https://gitlab.com/preserves/preserves">
|
||||
<span class="icon">
|
||||
<img src="{{ site.baseurl }}/gitlab-logo-500.svg" alt="Gitlab logo">
|
||||
<img src="{{ site.baseurl }}/gitlab-logo-500.svg" height="64" alt="Gitlab logo">
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -38,7 +38,7 @@ representations of their keys.[^no-need-for-by-value]
|
|||
**Other kinds of `Value`.**
|
||||
There are no special canonicalization restrictions on
|
||||
`SignedInteger`s, `String`s, `ByteString`s, `Symbol`s, `Boolean`s,
|
||||
`Float`s, `Double`s, `Record`s, `Sequence`s, or `Embedded`s. The
|
||||
`Double`s, `Record`s, `Sequence`s, or `Embedded`s. The
|
||||
constraints given for these `Value`s in the [specification][spec]
|
||||
suffice to ensure canonicity.
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
no_site_title: true
|
||||
title: "Preserves Quick Reference (Plaintext)"
|
||||
---
|
||||
|
||||
Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
{{ site.version_date }}. Version {{ site.version }}.
|
||||
|
||||
These are non-normative "reference card" definitions. See also the normative [semantics]({{
|
||||
site.baseurl }}/preserves.html), [text syntax specification]({{ site.baseurl
|
||||
}}/preserves-text.html), and [machine-oriented syntax specification]({{ site.baseurl
|
||||
}}/preserves-binary.html), and the experimental [P-expressions definition]({{ site.baseurl
|
||||
}}/preserves-expressions.html).
|
||||
|
||||
## <a id="binary"></a>Machine-Oriented Binary Syntax
|
||||
|
||||
{% include cheatsheet-binary-plaintext.md %}
|
||||
|
||||
## <a id="text"></a>Human-Oriented Text Syntax
|
||||
|
||||
{% include cheatsheet-text-plaintext.md %}
|
||||
|
||||
## <a id="pexprs"></a>P-expression Syntax
|
||||
|
||||
{% include cheatsheet-pexprs-plaintext.md %}
|
|
@ -1,11 +1,25 @@
|
|||
---
|
||||
no_site_title: true
|
||||
title: "Preserves Cheatsheets"
|
||||
title: "Preserves Quick Reference"
|
||||
---
|
||||
|
||||
Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
{{ site.version_date }}. Version {{ site.version }}.
|
||||
|
||||
## Machine-Oriented Binary Syntax
|
||||
These are non-normative "reference card" definitions. See also the normative [semantics]({{
|
||||
site.baseurl }}/preserves.html), [text syntax specification]({{ site.baseurl
|
||||
}}/preserves-text.html), and [machine-oriented syntax specification]({{ site.baseurl
|
||||
}}/preserves-binary.html), and the experimental [P-expressions definition]({{ site.baseurl
|
||||
}}/preserves-expressions.html).
|
||||
|
||||
## <a id="binary"></a>Machine-Oriented Binary Syntax
|
||||
|
||||
{% include cheatsheet-binary.md %}
|
||||
|
||||
## <a id="text"></a>Human-Oriented Text Syntax
|
||||
|
||||
{% include cheatsheet-text.md %}
|
||||
|
||||
## <a id="pexprs"></a>P-expression Syntax
|
||||
|
||||
{% include cheatsheet-pexprs.md %}
|
||||
|
|
|
@ -23,10 +23,10 @@ Appropriately-labelled `Record`s denote these domain-specific data
|
|||
types.[^why-dictionaries]
|
||||
|
||||
[^why-dictionaries]: Given `Record`'s existence, it may seem odd
|
||||
that `Dictionary`, `Set`, `Float`, etc. are given special
|
||||
that `Dictionary`, `Set`, `Double`, etc. are given special
|
||||
treatment. Preserves aims to offer a useful basic equivalence
|
||||
predicate to programmers, and so if a data type demands a special
|
||||
equivalence predicate, as `Dictionary`, `Set` and `Float` all do,
|
||||
equivalence predicate, as `Dictionary`, `Set` and `Double` all do,
|
||||
then the type should be included in the base language. Otherwise,
|
||||
it can be represented as a `Record` and treated separately.
|
||||
`Boolean`, `String` and `Symbol` are seeming exceptions. The first
|
||||
|
@ -74,28 +74,47 @@ interior portions of a tree.
|
|||
comments. Special syntax exists for such string annotations, though
|
||||
the usual `@`-prefixed annotation notation can also be used.
|
||||
|
||||
;I am a comment for the Dictionary
|
||||
# I am a comment for the Dictionary
|
||||
{
|
||||
;I am a comment for the key
|
||||
key: ;I am a comment for the value
|
||||
# I am a comment for the key
|
||||
key: # I am a comment for the value
|
||||
value
|
||||
}
|
||||
|
||||
;I am a comment for this entire IOList
|
||||
# I am a comment for this entire IOList, as are the next three lines.
|
||||
#
|
||||
# The previous line (containing only hash-newline) adds an empty
|
||||
# string to the annotations attached to the entire IOList.
|
||||
[
|
||||
#x"00010203"
|
||||
;I am a comment for the middle half of the IOList
|
||||
;A second comment for the same portion of the IOList
|
||||
@ ;I am the first and only comment for the following comment
|
||||
# I am a comment for the middle half of the IOList
|
||||
# A second comment for the same portion of the IOList
|
||||
@ # I am the first and only comment for the following comment
|
||||
"A third (itself commented!) comment for the same part of the IOList"
|
||||
[
|
||||
;"I am a comment for the following ByteString"
|
||||
# I am a comment for the following ByteString
|
||||
#x"04050607"
|
||||
#x"08090A0B"
|
||||
]
|
||||
#x"0C0D0E0F"
|
||||
]
|
||||
|
||||
## Interpreter specification lines ("shebang" lines).
|
||||
|
||||
Unix systems interpret `#!` at the beginning of an executable file specially. The text
|
||||
following `#!` on the first line is interpreted as a specification for an interpreter for the
|
||||
executable file. Preserves offers special support for `#!`, reading it similarly to a comment,
|
||||
but producing an `<interpreter ...>` annotation instead of a string.
|
||||
|
||||
For example,
|
||||
|
||||
#!/usr/bin/preserves-tool convert
|
||||
[1, 2, 3]
|
||||
|
||||
is read as
|
||||
|
||||
@<interpreter "/usr/bin/preserves-tool convert"> [1, 2, 3]
|
||||
|
||||
## MIME-type tagged binary data.
|
||||
|
||||
Many internet protocols use
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
/build.python/
|
||||
/*.deb
|
|
@ -0,0 +1,10 @@
|
|||
PYTHON_PACKAGEVERSION := $(shell ../implementations/python/print-package-version)
|
||||
|
||||
all: python3-preserves_$(PYTHON_PACKAGEVERSION)_all.deb
|
||||
|
||||
python3-preserves_%_all.deb:
|
||||
./build-python-deb
|
||||
|
||||
clean:
|
||||
rm -f *.deb
|
||||
rm -rf build.*
|
|
@ -0,0 +1,37 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
PYTHON_PACKAGEVERSION=$(../implementations/python/print-package-version)
|
||||
DIRTY=
|
||||
if ! git diff-index --quiet HEAD --
|
||||
then
|
||||
DIRTY=+
|
||||
fi
|
||||
GITSUFFIX=$(git log --date=format:%Y%m%d%H%M%S --pretty=~git%cd.%h -1)
|
||||
VERSION=${PYTHON_PACKAGEVERSION}${GITSUFFIX}${DIRTY}
|
||||
|
||||
echo "Building deb for ${VERSION}"
|
||||
(cd ../implementations/python && . ./.envrc && make build-only)
|
||||
|
||||
rm -rf build.python
|
||||
mkdir build.python
|
||||
|
||||
(
|
||||
cd build.python
|
||||
tar -zxvf ../../implementations/python/dist/preserves-${PYTHON_PACKAGEVERSION}.tar.gz
|
||||
(
|
||||
cd preserves-${PYTHON_PACKAGEVERSION}
|
||||
cp -a ../../python ./debian
|
||||
cat > ./debian/changelog <<EOF
|
||||
preserves (${VERSION}) UNRELEASED; urgency=low
|
||||
|
||||
* Unofficial debian packaging of Python Preserves
|
||||
|
||||
-- Tony Garnock-Jones <tonyg@leastfixedpoint.com> $(date --rfc-email)
|
||||
EOF
|
||||
|
||||
dpkg-buildpackage
|
||||
)
|
||||
cp *.deb ..
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
Source: preserves
|
||||
Section: python
|
||||
Priority: optional
|
||||
Maintainer: Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
Build-Depends: debhelper-compat (= 12), python3, dh-python, pybuild-plugin-pyproject
|
||||
Standards-Version: 3.9.3
|
||||
Homepage: https://preserves.dev/
|
||||
Vcs-Git: https://gitlab.com/preserves/preserves.git
|
||||
Vcs-Browser: https://gitlab.com/preserves/preserves
|
||||
|
||||
Package: python3-preserves
|
||||
Architecture: all
|
||||
Depends: ${python3:Depends}
|
||||
Description: Python implementation of the Preserves data language
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/make -f
|
||||
#export DH_VERBOSE=1
|
||||
export PYBUILD_NAME=preserves
|
||||
|
||||
%:
|
||||
dh $@ --with python3 --buildsystem=pybuild
|
|
@ -14,7 +14,7 @@ inputs.
|
|||
You will usually not need to use the `preserves-schema-rs`
|
||||
command-line program. Instead, access the preserves-schema compiler
|
||||
API from your `build.rs`. The following example is taken from
|
||||
[`build.rs` for the `preserves-path` crate](https://gitlab.com/preserves/preserves/-/blob/18ac9168996026073ee16164fce108054b2a0ed7/implementations/rust/preserves-path/build.rs):
|
||||
[`build.rs` for the `preserves-path` crate](https://gitlab.com/preserves/preserves/-/blob/af5de5b836ffc51999db93797d1995ff677cf6f8/implementations/rust/preserves-path/build.rs):
|
||||
|
||||
use preserves_schema::compiler::*;
|
||||
|
||||
|
@ -30,14 +30,14 @@ API from your `build.rs`. The following example is taken from
|
|||
let mut c = CompilerConfig::new(gen_dir, "crate::schemas".to_owned());
|
||||
|
||||
let inputs = expand_inputs(&vec!["path.bin".to_owned()])?;
|
||||
c.load_schemas_and_bundles(&inputs)?;
|
||||
c.load_schemas_and_bundles(&inputs, &vec![])?;
|
||||
|
||||
compile(&c)
|
||||
}
|
||||
|
||||
This approach also requires an `include!` from your main, hand-written
|
||||
source tree. The following is a snippet from
|
||||
[`preserves-path/src/lib.rs`](https://gitlab.com/preserves/preserves/-/blob/18ac9168996026073ee16164fce108054b2a0ed7/implementations/rust/preserves-path/src/lib.rs):
|
||||
[`preserves-path/src/lib.rs`](https://gitlab.com/preserves/preserves/-/blob/af5de5b836ffc51999db93797d1995ff677cf6f8/implementations/rust/preserves-path/src/lib.rs):
|
||||
|
||||
pub mod schemas {
|
||||
include!(concat!(env!("OUT_DIR"), "/src/schemas/mod.rs"));
|
||||
|
@ -52,20 +52,23 @@ Then, `cargo install preserves-schema`.
|
|||
|
||||
## Usage
|
||||
|
||||
preserves-schema 1.0.0
|
||||
preserves-schema 3.990.2
|
||||
|
||||
USAGE:
|
||||
preserves-schema-rs [OPTIONS] --output-dir <output-dir> --prefix <prefix> [--] [input-glob]...
|
||||
preserves-schema-rs [FLAGS] [OPTIONS] --output-dir <output-dir> --prefix <prefix>
|
||||
[--] [input-glob]...
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
-h, --help Prints help information
|
||||
--rustfmt-skip
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--module <module>...
|
||||
-o, --output-dir <output-dir>
|
||||
-p, --prefix <prefix>
|
||||
--support-crate <support-crate>
|
||||
--xref <xref>...
|
||||
|
||||
ARGS:
|
||||
<input-glob>...
|
||||
|
|
|
@ -5,20 +5,29 @@ title: preserves-tool
|
|||
The `preserves-tool` program is a swiss army knife for working with
|
||||
Preserves documents.
|
||||
|
||||
preserves-tools 1.0.0
|
||||
```
|
||||
preserves-tool 4.992.0
|
||||
Swiss-army knife tool for working with Preserves data.
|
||||
See https://preserves.dev/. If no subcommand is specified, the default
|
||||
subcommand will be `convert`.
|
||||
|
||||
USAGE:
|
||||
preserves-tool <SUBCOMMAND>
|
||||
USAGE:
|
||||
preserves-tool [OPTIONS]
|
||||
preserves-tool <SUBCOMMAND>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Print help information
|
||||
-V, --version Print version information
|
||||
OPTIONS:
|
||||
-h, --help Print help information
|
||||
-V, --version Print version information
|
||||
|
||||
SUBCOMMANDS:
|
||||
completions
|
||||
convert
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
quote
|
||||
OPTIONS FOR DEFAULT SUBCOMMAND convert:
|
||||
[...]
|
||||
|
||||
SUBCOMMANDS:
|
||||
completions
|
||||
convert
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
quote
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -31,9 +40,10 @@ Then, `cargo install preserves-tools`.
|
|||
|
||||
The tool includes three subcommands.
|
||||
|
||||
### `preserves-tool convert`
|
||||
### `preserves-tool convert`, `preserves-tool`
|
||||
|
||||
This is the main tool. It can
|
||||
This is the main tool, and is also the default if no subcommand is
|
||||
explicitly specified. It can
|
||||
|
||||
- translate between the various Preserves text and binary document
|
||||
syntaxes;
|
||||
|
@ -48,38 +58,45 @@ This is the main tool. It can
|
|||
USAGE:
|
||||
preserves-tool convert [FLAGS] [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
--collect
|
||||
--escape-spaces
|
||||
-h, --help Prints help information
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--bundle <filename>...
|
||||
-c, --commas <commas>
|
||||
--bundle <filename>
|
||||
|
||||
|
||||
-c, --commas <COMMAS>
|
||||
[default: none] [possible values: none, separating, terminating]
|
||||
|
||||
--indent <on/off>
|
||||
[default: on] [possible values: disabled, no, n, off, 0, false,
|
||||
enabled, yes, y, on, 1, true]
|
||||
--collect
|
||||
|
||||
-i, --input-format <input-format>
|
||||
|
||||
--escape-spaces
|
||||
|
||||
|
||||
-h, --help
|
||||
Print help information
|
||||
|
||||
-i, --input-format <INPUT_FORMAT>
|
||||
[default: auto-detect] [possible values: auto-detect, text, binary]
|
||||
|
||||
--limit <limit>
|
||||
-o, --output-format <output-format>
|
||||
[default: text] [possible values: text, binary, unquoted]
|
||||
--read-annotations <on/off>
|
||||
[default: on] [possible values: disabled, no, n, off, 0, false,
|
||||
enabled, yes, y, on, 1, true]
|
||||
--indent <on/off>
|
||||
[default: on] [possible values: disabled, enabled]
|
||||
|
||||
--schema <schema>
|
||||
--select <select-expr> [default: *]
|
||||
--select-output <select-output>
|
||||
--limit <LIMIT>
|
||||
|
||||
|
||||
-o, --output-format <OUTPUT_FORMAT>
|
||||
[default: text] [possible values: text, binary, unquoted]
|
||||
|
||||
--read-annotations <on/off>
|
||||
[default: on] [possible values: disabled, enabled]
|
||||
|
||||
--select <SELECT_EXPR>
|
||||
[default: *]
|
||||
|
||||
--select-output <SELECT_OUTPUT>
|
||||
[default: sequence] [possible values: sequence, set]
|
||||
|
||||
--write-annotations <on/off>
|
||||
[default: on] [possible values: disabled, no, n, off, 0, false,
|
||||
enabled, yes, y, on, 1, true]
|
||||
[default: on] [possible values: disabled, enabled]
|
||||
|
||||
### `preserves-tool quote`
|
||||
|
||||
|
@ -101,12 +118,10 @@ preserves-tool-quote
|
|||
USAGE:
|
||||
preserves-tool quote [OPTIONS] <SUBCOMMAND>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Print help information
|
||||
-V, --version Print version information
|
||||
|
||||
OPTIONS:
|
||||
-o, --output-format <OUTPUT_FORMAT> [default: text] [possible values: text, binary, unquoted]
|
||||
-h, --help Print help information
|
||||
-o, --output-format <OUTPUT_FORMAT> [default: text] [possible values: text,
|
||||
binary, unquoted]
|
||||
|
||||
SUBCOMMANDS:
|
||||
byte-string
|
||||
|
@ -119,32 +134,28 @@ SUBCOMMANDS:
|
|||
preserves-tool-quote-string
|
||||
|
||||
USAGE:
|
||||
preserves-tool quote string [FLAGS] [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
--escape-spaces
|
||||
-h, --help Print help information
|
||||
--include-terminator
|
||||
-V, --version Print version information
|
||||
preserves-tool quote string [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--input-terminator <INPUT_TERMINATOR> [default: eof] [possible values: eof, newline, nul]
|
||||
--escape-spaces
|
||||
-h, --help Print help information
|
||||
--include-terminator
|
||||
--input-terminator <INPUT_TERMINATOR> [default: eof] [possible values:
|
||||
eof, newline, nul]
|
||||
```
|
||||
|
||||
```
|
||||
preserves-tool-quote-symbol
|
||||
|
||||
USAGE:
|
||||
preserves-tool quote symbol [FLAGS] [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
--escape-spaces
|
||||
-h, --help Print help information
|
||||
--include-terminator
|
||||
-V, --version Print version information
|
||||
preserves-tool quote symbol [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--input-terminator <INPUT_TERMINATOR> [default: eof] [possible values: eof, newline, nul]
|
||||
--escape-spaces
|
||||
-h, --help Print help information
|
||||
--include-terminator
|
||||
--input-terminator <INPUT_TERMINATOR> [default: eof] [possible values:
|
||||
eof, newline, nul]
|
||||
```
|
||||
|
||||
```
|
||||
|
@ -153,9 +164,8 @@ preserves-tool-quote-byte-string
|
|||
USAGE:
|
||||
preserves-tool quote byte-string
|
||||
|
||||
FLAGS:
|
||||
-h, --help Print help information
|
||||
-V, --version Print version information
|
||||
OPTIONS:
|
||||
-h, --help Print help information
|
||||
```
|
||||
|
||||
### `preserves-tool completions`
|
||||
|
@ -176,12 +186,11 @@ Multiple shell dialects are supported (courtesy of
|
|||
preserves-tool-completions
|
||||
|
||||
USAGE:
|
||||
preserves-tool completions <dialect>
|
||||
preserves-tool completions <SHELL>
|
||||
|
||||
ARGS:
|
||||
<dialect> [possible values: bash, zsh, power-shell, fish, elvish]
|
||||
<SHELL> [possible values: bash, elvish, fish, powershell, zsh]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Print help information
|
||||
-V, --version Print version information
|
||||
OPTIONS:
|
||||
-h, --help Print help information
|
||||
```
|
||||
|
|
|
@ -3,6 +3,20 @@
|
|||
set -e
|
||||
exec 1>&2
|
||||
|
||||
failed=
|
||||
cmp_and_fail() {
|
||||
if ! cmp "$1" "$2"
|
||||
then
|
||||
failed=failed
|
||||
fi
|
||||
}
|
||||
|
||||
COMMAND=cmp_and_fail
|
||||
if [ "$1" = "--fix" ];
|
||||
then
|
||||
COMMAND=cp
|
||||
fi
|
||||
|
||||
# https://gitlab.com/preserves/preserves/-/issues/30
|
||||
#
|
||||
# So it turns out that Racket's git-checkout mechanism pays attention
|
||||
|
@ -16,10 +30,13 @@ exec 1>&2
|
|||
|
||||
# Ensure that various copies of schema.prs, schema.bin, path.bin,
|
||||
# samples.pr and samples.bin are in fact identical.
|
||||
cmp path/path.bin implementations/python/preserves/path.prb
|
||||
cmp path/path.bin implementations/rust/preserves-path/path.bin
|
||||
cmp schema/schema.bin implementations/python/preserves/schema.prb
|
||||
cmp schema/schema.prs implementations/racket/preserves/preserves-schema/schema.prs
|
||||
cmp tests/samples.bin implementations/python/tests/samples.bin
|
||||
cmp tests/samples.pr implementations/python/tests/samples.pr
|
||||
cmp tests/samples.pr implementations/racket/preserves/preserves/tests/samples.pr
|
||||
${COMMAND} path/path.bin implementations/python/preserves/path.prb
|
||||
|
||||
${COMMAND} schema/schema.bin implementations/python/preserves/schema.prb
|
||||
${COMMAND} schema/schema.prs implementations/racket/preserves/preserves-schema/schema.prs
|
||||
|
||||
${COMMAND} tests/samples.bin implementations/python/tests/samples.bin
|
||||
${COMMAND} tests/samples.pr implementations/python/tests/samples.pr
|
||||
${COMMAND} tests/samples.pr implementations/racket/preserves/preserves/tests/samples.pr
|
||||
|
||||
[ -z "$failed" ]
|
||||
|
|
|
@ -13,9 +13,9 @@ Here you may find:
|
|||
- [racket](racket/), an implementation for Racket 7.x and newer
|
||||
(though older Rackets may also work with it).
|
||||
|
||||
- [rust](rust/), an implementation for Rust that interoperates with
|
||||
serde.
|
||||
|
||||
Other implementations are also available:
|
||||
|
||||
- [Preserves for Rust](https://gitlab.com/preserves/preserves-rs/), an implementation for Rust
|
||||
that interoperates with serde.
|
||||
|
||||
- [Preserves for Squeak Smalltalk](https://squeaksource.com/Preserves.html)
|
||||
|
|
|
@ -218,19 +218,12 @@ PRESERVES_INLINE int preserves_pool_alloc_bytes(preserves_pool_t *pool,
|
|||
typedef enum preserves_binary_format_tag {
|
||||
PRESERVES_BINARY_FORMAT_TAG_FALSE = 0x80,
|
||||
PRESERVES_BINARY_FORMAT_TAG_TRUE = 0x81,
|
||||
PRESERVES_BINARY_FORMAT_TAG_FLOAT = 0x82,
|
||||
PRESERVES_BINARY_FORMAT_TAG_DOUBLE = 0x83,
|
||||
PRESERVES_BINARY_FORMAT_TAG_END = 0x84,
|
||||
PRESERVES_BINARY_FORMAT_TAG_ANNOTATION = 0x85,
|
||||
PRESERVES_BINARY_FORMAT_TAG_EMBEDDED = 0x86,
|
||||
PRESERVES_BINARY_FORMAT_TAG_IEEE754 = 0x87,
|
||||
|
||||
PRESERVES_BINARY_FORMAT_TAG_SMALL_INTEGER_LO = 0x90,
|
||||
PRESERVES_BINARY_FORMAT_TAG_SMALL_INTEGER_HI = 0x9F,
|
||||
|
||||
PRESERVES_BINARY_FORMAT_TAG_MEDIUM_INTEGER_LO = 0xA0,
|
||||
PRESERVES_BINARY_FORMAT_TAG_MEDIUM_INTEGER_HI = 0xAF,
|
||||
|
||||
PRESERVES_BINARY_FORMAT_TAG_LARGE_INTEGER = 0xB0,
|
||||
PRESERVES_BINARY_FORMAT_TAG_SIGNED_INTEGER = 0xB0,
|
||||
PRESERVES_BINARY_FORMAT_TAG_STRING = 0xB1,
|
||||
PRESERVES_BINARY_FORMAT_TAG_BYTE_STRING = 0xB2,
|
||||
PRESERVES_BINARY_FORMAT_TAG_SYMBOL = 0xB3,
|
||||
|
@ -247,12 +240,11 @@ PRESERVES_OUTOFLINE
|
|||
switch (tag) {
|
||||
case PRESERVES_BINARY_FORMAT_TAG_FALSE: return "FALSE";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_TRUE: return "TRUE";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_FLOAT: return "FLOAT";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_DOUBLE: return "DOUBLE";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_END: return "END";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_ANNOTATION: return "ANNOTATION";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_EMBEDDED: return "EMBEDDED";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_LARGE_INTEGER: return "LARGE_INTEGER";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_IEEE754: return "IEEE754";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_SIGNED_INTEGER: return "SIGNED_INTEGER";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_STRING: return "STRING";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_BYTE_STRING: return "BYTE_STRING";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_SYMBOL: return "SYMBOL";
|
||||
|
@ -260,16 +252,7 @@ PRESERVES_OUTOFLINE
|
|||
case PRESERVES_BINARY_FORMAT_TAG_SEQUENCE: return "SEQUENCE";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_SET: return "SET";
|
||||
case PRESERVES_BINARY_FORMAT_TAG_DICTIONARY: return "DICTIONARY";
|
||||
default:
|
||||
if ((tag >= PRESERVES_BINARY_FORMAT_TAG_SMALL_INTEGER_LO) &&
|
||||
(tag <= PRESERVES_BINARY_FORMAT_TAG_SMALL_INTEGER_HI)) {
|
||||
return "SMALL_INTEGER";
|
||||
} else if ((tag >= PRESERVES_BINARY_FORMAT_TAG_MEDIUM_INTEGER_LO) &&
|
||||
(tag <= PRESERVES_BINARY_FORMAT_TAG_MEDIUM_INTEGER_HI)) {
|
||||
return "MEDIUM_INTEGER";
|
||||
} else {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -278,7 +261,6 @@ PRESERVES_OUTOFLINE
|
|||
|
||||
typedef enum preserves_type_tag {
|
||||
PRESERVES_BOOLEAN = 0,
|
||||
PRESERVES_FLOAT,
|
||||
PRESERVES_DOUBLE,
|
||||
|
||||
PRESERVES_SIGNED_INTEGER,
|
||||
|
@ -300,7 +282,6 @@ typedef enum preserves_type_tag {
|
|||
PRESERVES_OUTOFLINE(char const *preserves_type_tag_name(preserves_type_tag_t type), {
|
||||
switch (type) {
|
||||
case PRESERVES_BOOLEAN: return "BOOLEAN";
|
||||
case PRESERVES_FLOAT: return "FLOAT";
|
||||
case PRESERVES_DOUBLE: return "DOUBLE";
|
||||
case PRESERVES_SIGNED_INTEGER: return "SIGNED_INTEGER";
|
||||
case PRESERVES_STRING: return "STRING";
|
||||
|
@ -331,6 +312,7 @@ typedef enum preserves_error_code {
|
|||
PRESERVES_END_VARINT_TOO_BIG,
|
||||
PRESERVES_END_INVALID_UTF8,
|
||||
PRESERVES_END_INVALID_TAG,
|
||||
PRESERVES_END_INVALID_IEEE754,
|
||||
} preserves_error_code_t;
|
||||
|
||||
PRESERVES_OUTOFLINE(char const *preserves_error_code_name(preserves_error_code_t code), {
|
||||
|
@ -346,6 +328,7 @@ PRESERVES_OUTOFLINE(char const *preserves_error_code_name(preserves_error_code_t
|
|||
case PRESERVES_END_VARINT_TOO_BIG: return "VARINT_TOO_BIG";
|
||||
case PRESERVES_END_INVALID_UTF8: return "INVALID_UTF8";
|
||||
case PRESERVES_END_INVALID_TAG: return "INVALID_TAG";
|
||||
case PRESERVES_END_INVALID_IEEE754: return "INVALID_IEEE754";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
});
|
||||
|
@ -381,7 +364,6 @@ PRESERVES_OUTOFLINE
|
|||
|
||||
/*
|
||||
PRESERVES_BOOLEAN: repr==PRESERVES_REPR_NONE, len=0, data._boolean
|
||||
PRESERVES_FLOAT: repr=PRESERVES_REPR_NONE, len=0, data._float
|
||||
PRESERVES_DOUBLE: repr=PRESERVES_REPR_NONE, len=0, data._double
|
||||
|
||||
PRESERVES_SIGNED_INTEGER:
|
||||
|
@ -418,7 +400,7 @@ PRESERVES_OUTOFLINE
|
|||
|
||||
PRESERVES_EMBEDDED: repr==PRESERVES_REPR_NONE, len==0, following item is the embedded value
|
||||
PRESERVES_ANNOTATION:
|
||||
- repr=PRESERVES_REPR_NONE,
|
||||
- repr==PRESERVES_REPR_NONE,
|
||||
- len counts number of annotations,
|
||||
- data._unsigned as relative offset within index to annotated
|
||||
item, starting from this entry; zero means "no end known"
|
||||
|
@ -433,7 +415,6 @@ typedef struct preserves_index_entry {
|
|||
|
||||
union {
|
||||
bool _boolean;
|
||||
float _float;
|
||||
double _double;
|
||||
int64_t _signed;
|
||||
uint64_t _unsigned;
|
||||
|
@ -501,8 +482,8 @@ PRESERVES_IMPLEMENTATION_CHUNK
|
|||
}
|
||||
|
||||
static inline int _preserves_reader_next(preserves_reader_t *r) {
|
||||
if (r->input_pos >= r->input.len) return -1;
|
||||
int result = PRESERVES_ARRAY_ELEMENT(&r->input, uint8_t, r->input_pos);
|
||||
int result = _preserves_reader_peek(r);
|
||||
if (result == -1) return -1;
|
||||
r->input_pos++;
|
||||
return result;
|
||||
}
|
||||
|
@ -618,19 +599,25 @@ PRESERVES_IMPLEMENTATION_CHUNK
|
|||
}
|
||||
}
|
||||
|
||||
static inline int _preserves_reader_varint(preserves_reader_t *r, size_t *v) {
|
||||
static inline size_t _preserves_reader_varint(preserves_reader_t *r, preserves_error_code_t *code) {
|
||||
unsigned int shift_amount = 0;
|
||||
size_t result = 0;
|
||||
while (true) {
|
||||
int b = _preserves_reader_next(r);
|
||||
if (b == -1) return -1;
|
||||
if (b == -1) {
|
||||
*code = PRESERVES_END_INCOMPLETE_INPUT;
|
||||
return 0;
|
||||
}
|
||||
result |= (b & 0x7f) << shift_amount;
|
||||
if (b & 0x80) {
|
||||
shift_amount += 7;
|
||||
if (shift_amount > ((sizeof(size_t) * 8) - 7)) return -2;
|
||||
if (shift_amount > ((sizeof(size_t) * 8) - 7)) {
|
||||
*code = PRESERVES_END_VARINT_TOO_BIG;
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
*v = result;
|
||||
return 0;
|
||||
*code = PRESERVES_END_NO_ERROR;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -742,12 +729,9 @@ PRESERVES_IMPLEMENTATION_CHUNK
|
|||
size_t *count_ptr,
|
||||
preserves_type_tag_t type,
|
||||
bool should_check_utf8) {
|
||||
size_t len = 0;
|
||||
switch (_preserves_reader_varint(r, &len)) {
|
||||
case -1: return PRESERVES_END_INCOMPLETE_INPUT;
|
||||
case -2: return PRESERVES_END_VARINT_TOO_BIG;
|
||||
default: break;
|
||||
}
|
||||
preserves_error_code_t varint_err = PRESERVES_END_NO_ERROR;
|
||||
size_t len = _preserves_reader_varint(r, &varint_err);
|
||||
if (varint_err != PRESERVES_END_NO_ERROR) return varint_err;
|
||||
size_t starting_pos = r->input_pos;
|
||||
uint8_t *maybe_utf = _preserves_reader_next_bytes(r, len);
|
||||
if (should_check_utf8 && (check_utf8(maybe_utf, len) == -1)) {
|
||||
|
@ -823,36 +807,31 @@ PRESERVES_OUTOFLINE
|
|||
}}));
|
||||
break;
|
||||
|
||||
case PRESERVES_BINARY_FORMAT_TAG_FLOAT: {
|
||||
uint8_t *bs = _preserves_reader_next_bytes(r, 4);
|
||||
case PRESERVES_BINARY_FORMAT_TAG_IEEE754: {
|
||||
preserves_error_code_t varint_err = PRESERVES_END_NO_ERROR;
|
||||
size_t len = _preserves_reader_varint(r, &varint_err);
|
||||
if (varint_err != PRESERVES_END_NO_ERROR) return _preserves_reader_finish(r, varint_err);
|
||||
uint8_t *bs = _preserves_reader_next_bytes(r, len);
|
||||
if (bs == NULL) return _preserves_reader_finish(r, PRESERVES_END_INCOMPLETE_INPUT);
|
||||
uint32_t i;
|
||||
memcpy(&i, bs, 4);
|
||||
i = ntohl(i);
|
||||
float f;
|
||||
memcpy(&f, &i, 4);
|
||||
RETURN_ON_FAIL(_preserves_reader_emit_entry(r, &count, (preserves_index_entry_t) {
|
||||
.type = PRESERVES_FLOAT, .repr = PRESERVES_REPR_NONE, .len = 0, .data = {
|
||||
._float = f
|
||||
}}));
|
||||
break;
|
||||
}
|
||||
|
||||
case PRESERVES_BINARY_FORMAT_TAG_DOUBLE: {
|
||||
uint8_t *bs = _preserves_reader_next_bytes(r, 8);
|
||||
if (bs == NULL) return _preserves_reader_finish(r, PRESERVES_END_INCOMPLETE_INPUT);
|
||||
uint32_t lo, hi;
|
||||
memcpy(&hi, bs, 4);
|
||||
memcpy(&lo, bs + 4, 4);
|
||||
lo = ntohl(lo);
|
||||
hi = ntohl(hi);
|
||||
uint64_t i = (((uint64_t) hi) << 32) | ((uint64_t) lo);
|
||||
double f;
|
||||
memcpy(&f, &i, 8);
|
||||
RETURN_ON_FAIL(_preserves_reader_emit_entry(r, &count, (preserves_index_entry_t) {
|
||||
.type = PRESERVES_DOUBLE, .repr = PRESERVES_REPR_NONE, .len = 0, .data = {
|
||||
._double = f
|
||||
}}));
|
||||
switch (len) {
|
||||
case 8: {
|
||||
uint32_t lo, hi;
|
||||
memcpy(&hi, bs, 4);
|
||||
memcpy(&lo, bs + 4, 4);
|
||||
lo = ntohl(lo);
|
||||
hi = ntohl(hi);
|
||||
uint64_t i = (((uint64_t) hi) << 32) | ((uint64_t) lo);
|
||||
double f;
|
||||
memcpy(&f, &i, 8);
|
||||
RETURN_ON_FAIL(_preserves_reader_emit_entry(r, &count, (preserves_index_entry_t) {
|
||||
.type = PRESERVES_DOUBLE, .repr = PRESERVES_REPR_NONE, .len = 0, .data = {
|
||||
._double = f
|
||||
}}));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return _preserves_reader_finish(r, PRESERVES_END_INVALID_IEEE754);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -881,13 +860,10 @@ PRESERVES_OUTOFLINE
|
|||
RETURN_ON_FAIL(_preserves_reader_push(r, PRESERVES_EMBEDDED));
|
||||
break;
|
||||
|
||||
case PRESERVES_BINARY_FORMAT_TAG_LARGE_INTEGER: {
|
||||
size_t len = 0;
|
||||
switch (_preserves_reader_varint(r, &len)) {
|
||||
case -1: return _preserves_reader_finish(r, PRESERVES_END_INCOMPLETE_INPUT);
|
||||
case -2: return _preserves_reader_finish(r, PRESERVES_END_VARINT_TOO_BIG);
|
||||
default: break;
|
||||
}
|
||||
case PRESERVES_BINARY_FORMAT_TAG_SIGNED_INTEGER: {
|
||||
preserves_error_code_t varint_err = PRESERVES_END_NO_ERROR;
|
||||
size_t len = _preserves_reader_varint(r, &varint_err);
|
||||
if (varint_err != PRESERVES_END_NO_ERROR) return _preserves_reader_finish(r, varint_err);
|
||||
if (_preserves_reader_decode_intbytes(r, &count, len) == -1) {
|
||||
return _preserves_reader_finish(r, PRESERVES_END_INCOMPLETE_INPUT);
|
||||
}
|
||||
|
@ -932,21 +908,7 @@ PRESERVES_OUTOFLINE
|
|||
break;
|
||||
|
||||
default:
|
||||
if ((b >= PRESERVES_BINARY_FORMAT_TAG_SMALL_INTEGER_LO) &&
|
||||
(b <= PRESERVES_BINARY_FORMAT_TAG_SMALL_INTEGER_HI)) {
|
||||
int64_t value = b - PRESERVES_BINARY_FORMAT_TAG_SMALL_INTEGER_LO;
|
||||
if (value > 12) value -= 16;
|
||||
RETURN_ON_FAIL(_preserves_emit_small_int(r, &count, false, value));
|
||||
} else if ((b >= PRESERVES_BINARY_FORMAT_TAG_MEDIUM_INTEGER_LO) &&
|
||||
(b <= PRESERVES_BINARY_FORMAT_TAG_MEDIUM_INTEGER_HI)) {
|
||||
size_t len = (b - PRESERVES_BINARY_FORMAT_TAG_MEDIUM_INTEGER_LO) + 1;
|
||||
if (_preserves_reader_decode_intbytes(r, &count, len) == -1) {
|
||||
return _preserves_reader_finish(r, PRESERVES_END_INCOMPLETE_INPUT);
|
||||
}
|
||||
} else {
|
||||
return _preserves_reader_finish(r, PRESERVES_END_INVALID_TAG);
|
||||
}
|
||||
break;
|
||||
return _preserves_reader_finish(r, PRESERVES_END_INVALID_TAG);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1017,10 +979,6 @@ PRESERVES_IMPLEMENTATION_CHUNK
|
|||
fprintf(f, i->data._boolean ? " #t" : " #f");
|
||||
break;
|
||||
|
||||
case PRESERVES_FLOAT:
|
||||
fprintf(f, " %f", i->data._float);
|
||||
break;
|
||||
|
||||
case PRESERVES_DOUBLE:
|
||||
fprintf(f, " %f", i->data._double);
|
||||
break;
|
||||
|
@ -1042,8 +1000,11 @@ PRESERVES_IMPLEMENTATION_CHUNK
|
|||
fprintf(f, " skip %lu", i->data._unsigned - 1);
|
||||
break;
|
||||
|
||||
case PRESERVES_EMBEDDED:
|
||||
case PRESERVES_ANNOTATION:
|
||||
fprintf(f, " annotated after %lu", i->data._unsigned - 1);
|
||||
break;
|
||||
|
||||
case PRESERVES_EMBEDDED:
|
||||
break;
|
||||
|
||||
case PRESERVES_END_MARKER:
|
||||
|
|
|
@ -24,14 +24,14 @@ TEST(Value, Basics) {
|
|||
}
|
||||
|
||||
TEST(BinaryReader, Negative257) {
|
||||
istringstream input("\xA1\xFE\xFF");
|
||||
istringstream input("\xB0\x02\xFE\xFF");
|
||||
auto v = BinaryReader<>(input).next();
|
||||
ASSERT_TRUE(v);
|
||||
ASSERT_EQ(v->to_signed(), -257);
|
||||
}
|
||||
|
||||
TEST(BinaryReader, Negative127) {
|
||||
istringstream input("\xA0\x81");
|
||||
istringstream input("\xB0\x01\x81");
|
||||
auto v = BinaryReader<>(input).next();
|
||||
ASSERT_TRUE(v);
|
||||
ASSERT_EQ(v->to_signed(), -127);
|
||||
|
@ -42,9 +42,10 @@ TEST(BinaryWriter, Negative257) {
|
|||
BinaryWriter w(s);
|
||||
w << -257;
|
||||
std::string output(s.str());
|
||||
ASSERT_EQ(output[0], char(BinaryTag::MediumInteger_lo) + 1);
|
||||
ASSERT_EQ(output[1], char(0xFE));
|
||||
ASSERT_EQ(output[2], char(0xFF));
|
||||
ASSERT_EQ(output[0], char(BinaryTag::SignedInteger));
|
||||
ASSERT_EQ(output[1], char(0x02));
|
||||
ASSERT_EQ(output[2], char(0xFE));
|
||||
ASSERT_EQ(output[3], char(0xFF));
|
||||
}
|
||||
|
||||
TEST(BinaryWriter, Negative127) {
|
||||
|
@ -52,8 +53,9 @@ TEST(BinaryWriter, Negative127) {
|
|||
BinaryWriter w(s);
|
||||
w << -127;
|
||||
std::string output(s.str());
|
||||
ASSERT_EQ(output[0], char(BinaryTag::MediumInteger_lo));
|
||||
ASSERT_EQ(output[1], char(0x81));
|
||||
ASSERT_EQ(output[0], char(BinaryTag::SignedInteger));
|
||||
ASSERT_EQ(output[1], char(0x01));
|
||||
ASSERT_EQ(output[2], char(0x81));
|
||||
}
|
||||
|
||||
TEST(BinaryReader, ReadSamples) {
|
||||
|
@ -62,4 +64,4 @@ TEST(BinaryReader, ReadSamples) {
|
|||
auto v = r.next();
|
||||
ASSERT_TRUE(v);
|
||||
// BinaryWriter(cerr) << *v;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,15 +41,6 @@ namespace Preserves {
|
|||
decodeEmbedded(decodeEmbedded)
|
||||
{}
|
||||
|
||||
boost::optional<float> next_float() {
|
||||
uint8_t buf[4];
|
||||
if (!next_chunk(buf, sizeof(buf))) return boost::none;
|
||||
uint32_t n = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
|
||||
float f;
|
||||
memcpy(&f, &n, sizeof(f));
|
||||
return f;
|
||||
}
|
||||
|
||||
boost::optional<double> next_double() {
|
||||
uint8_t buf[8];
|
||||
if (!next_chunk(buf, sizeof(buf))) return boost::none;
|
||||
|
@ -95,8 +86,6 @@ namespace Preserves {
|
|||
switch (tag) {
|
||||
case BinaryTag::False: return Value<T>::from_bool(false);
|
||||
case BinaryTag::True: return Value<T>::from_bool(true);
|
||||
case BinaryTag::Float: return next_float().map(Value<T>::from_float);
|
||||
case BinaryTag::Double: return next_double().map(Value<T>::from_double);
|
||||
case BinaryTag::End: end_sentinel = true; return boost::none;
|
||||
case BinaryTag::Annotation: {
|
||||
std::vector<Value<T>> annotations;
|
||||
|
@ -113,12 +102,14 @@ namespace Preserves {
|
|||
}
|
||||
case BinaryTag::Embedded:
|
||||
return BinaryReader<>(i).next().map(decodeEmbedded).map(Value<T>::from_embedded);
|
||||
case BinaryTag::SmallInteger_lo ... BinaryTag::SmallInteger_hi: {
|
||||
int64_t n = int64_t(tag) - int64_t(BinaryTag::SmallInteger_lo);
|
||||
return Value<T>::from_int(n <= 12 ? n : n - 16);
|
||||
}
|
||||
case BinaryTag::MediumInteger_lo ... BinaryTag::MediumInteger_hi: {
|
||||
int n = int(tag) - int(BinaryTag::MediumInteger_lo) + 1;
|
||||
case BinaryTag::Ieee754: return varint(i).flat_map([&](size_t len)-> boost::optional<Value<T>> {
|
||||
switch (len) {
|
||||
case 8: return next_double().map(Value<T>::from_double);
|
||||
default: return boost::none;
|
||||
}
|
||||
});
|
||||
case BinaryTag::SignedInteger: return varint(i).flat_map([&](size_t n)-> boost::optional<Value<T>> {
|
||||
if (n == 0) return Value<T>::from_int(uint64_t(0));
|
||||
if (n < 9) return next_machineword(n, false);
|
||||
if (n == 9) {
|
||||
// We can handle this with uint64_t if it's unsigned and the first byte is 0.
|
||||
|
@ -128,9 +119,6 @@ namespace Preserves {
|
|||
}
|
||||
}
|
||||
return next_bignum(n);
|
||||
}
|
||||
case BinaryTag::SignedInteger: return varint(i).flat_map([&](size_t len) {
|
||||
return next_bignum(len);
|
||||
});
|
||||
case BinaryTag::String: return varint(i).flat_map([&](size_t len)-> boost::optional<Value<T>> {
|
||||
auto s = std::make_shared<String<T>>(std::string());
|
||||
|
@ -192,6 +180,7 @@ namespace Preserves {
|
|||
s->values.emplace(*k, *v);
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return boost::none;
|
||||
}
|
||||
|
|
|
@ -11,15 +11,10 @@ namespace Preserves {
|
|||
enum class BinaryTag {
|
||||
False = 0x80,
|
||||
True = 0x81,
|
||||
Float = 0x82,
|
||||
Double = 0x83,
|
||||
End = 0x84,
|
||||
Annotation = 0x85,
|
||||
Embedded = 0x86,
|
||||
SmallInteger_lo = 0x90,
|
||||
SmallInteger_hi = 0x9f,
|
||||
MediumInteger_lo = 0xa0,
|
||||
MediumInteger_hi = 0xaf,
|
||||
Ieee754 = 0x87,
|
||||
SignedInteger = 0xb0,
|
||||
String = 0xb1,
|
||||
ByteString = 0xb2,
|
||||
|
@ -90,12 +85,9 @@ namespace Preserves {
|
|||
}
|
||||
|
||||
BinaryWriter& bignum(std::vector<uint8_t> const& n) {
|
||||
if (n.size() == 0) return (*this) << BinaryTag::SmallInteger_lo;
|
||||
size_t startOffset = 0;
|
||||
if (n[0] & 0x80) {
|
||||
if (n.size() == 1 && int8_t(n[0]) >= -3) {
|
||||
return put(uint8_t(int(BinaryTag::SmallInteger_lo) + 16 + int8_t(n[0])));
|
||||
} else {
|
||||
if (n.size() > 0) {
|
||||
if (n[0] & 0x80) {
|
||||
while (true) {
|
||||
if (startOffset == n.size() - 1) break;
|
||||
if (n[startOffset] != 0xff) break;
|
||||
|
@ -105,10 +97,6 @@ namespace Preserves {
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (n.size() == 1 && n[0] <= 12) {
|
||||
return put(uint8_t(int(BinaryTag::SmallInteger_lo) + n[0]));
|
||||
} else {
|
||||
while (true) {
|
||||
if (startOffset == n.size() - 1) break;
|
||||
|
@ -122,12 +110,8 @@ namespace Preserves {
|
|||
}
|
||||
}
|
||||
size_t count = n.size() - startOffset;
|
||||
if (count > 16) {
|
||||
(*this) << BinaryTag::SignedInteger;
|
||||
varint(count);
|
||||
} else {
|
||||
put(uint8_t(int(BinaryTag::MediumInteger_lo) + count - 1));
|
||||
}
|
||||
(*this) << BinaryTag::SignedInteger;
|
||||
varint(count);
|
||||
return write(&n[startOffset], count);
|
||||
}
|
||||
|
||||
|
@ -167,18 +151,6 @@ namespace Preserves {
|
|||
return (*this) << (b ? BinaryTag::True : BinaryTag::False);
|
||||
}
|
||||
|
||||
BinaryWriter& operator<<(float f) {
|
||||
uint32_t n;
|
||||
memcpy(&n, &f, sizeof(f));
|
||||
uint8_t buf[4];
|
||||
buf[0] = (n >> 24) & 0xff;
|
||||
buf[1] = (n >> 16) & 0xff;
|
||||
buf[2] = (n >> 8) & 0xff;
|
||||
buf[3] = (n) & 0xff;
|
||||
(*this) << BinaryTag::Float;
|
||||
return write(buf, sizeof(buf));
|
||||
}
|
||||
|
||||
BinaryWriter& operator<<(double d) {
|
||||
uint64_t n;
|
||||
memcpy(&n, &d, sizeof(d));
|
||||
|
@ -191,12 +163,14 @@ namespace Preserves {
|
|||
buf[5] = (n >> 16) & 0xff;
|
||||
buf[6] = (n >> 8) & 0xff;
|
||||
buf[7] = (n) & 0xff;
|
||||
(*this) << BinaryTag::Double;
|
||||
(*this) << BinaryTag::Ieee754;
|
||||
put(uint8_t(sizeof(buf)));
|
||||
return write(buf, sizeof(buf));
|
||||
}
|
||||
|
||||
BinaryWriter& _put_medium_int(uint64_t u, int shift, int extra) {
|
||||
put(uint8_t(int(BinaryTag::MediumInteger_lo) + (shift >> 3) + extra));
|
||||
(*this) << BinaryTag::SignedInteger;
|
||||
put(uint8_t((shift >> 3) + extra + 1));
|
||||
if (extra) put(0);
|
||||
while (shift >= 0) {
|
||||
put(uint8_t((u >> shift) & 0xff));
|
||||
|
@ -207,30 +181,19 @@ namespace Preserves {
|
|||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value, BinaryWriter&>::type operator<<(T t) {
|
||||
if (std::numeric_limits<T>::is_signed) {
|
||||
if (t == 0) {
|
||||
(*this) << BinaryTag::SignedInteger;
|
||||
put(0);
|
||||
return *this;
|
||||
} else if (std::numeric_limits<T>::is_signed) {
|
||||
auto i = static_cast<int64_t>(t);
|
||||
if (i < 0) {
|
||||
if (i >= -3) {
|
||||
return put(uint8_t(int(BinaryTag::SmallInteger_lo) + 16 + i));
|
||||
} else {
|
||||
uint64_t u;
|
||||
memcpy(&u, &i, sizeof(i));
|
||||
return _put_medium_int(u, _shift_for<0xff, 0x80>(u), 0);
|
||||
}
|
||||
} else {
|
||||
if (i <= 12) {
|
||||
return put(uint8_t(int(BinaryTag::SmallInteger_lo) + i));
|
||||
} else {
|
||||
uint64_t u;
|
||||
memcpy(&u, &i, sizeof(i));
|
||||
return _put_medium_int(u, _shift_for<0x00, 0x00>(u), 0);
|
||||
}
|
||||
}
|
||||
uint64_t u;
|
||||
memcpy(&u, &i, sizeof(i));
|
||||
int shift = i < 0 ? _shift_for<0xff, 0x80>(u) : _shift_for<0x00, 0x00>(u);
|
||||
return _put_medium_int(u, shift, 0);
|
||||
} else {
|
||||
auto u = static_cast<uint64_t>(t);
|
||||
if (u <= 12) {
|
||||
return put(uint8_t(int(BinaryTag::SmallInteger_lo) + u));
|
||||
} else if ((u & 0x8000000000000000) != 0) {
|
||||
if ((u & 0x8000000000000000) != 0) {
|
||||
return _put_medium_int(u, 56, 1);
|
||||
} else {
|
||||
return _put_medium_int(u, _shift_for<0x00, 0x00>(u), 0);
|
||||
|
@ -259,5 +222,11 @@ namespace Preserves {
|
|||
for (auto& i : vs) { (*this) << i.first << i.second; }
|
||||
return (*this) << BinaryTag::End;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
BinaryWriter& annotated(Value<T> const& underlying, std::vector<Value<T>> const& vs) {
|
||||
for (auto& v : vs) { (*this) << BinaryTag::Annotation << v; }
|
||||
return (*this) << underlying;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -35,13 +35,6 @@ namespace Preserves {
|
|||
BinaryWriter& write(BinaryWriter& w) const override {
|
||||
return w << this->_value();
|
||||
});
|
||||
PRESERVES_ATOMIC_VALUE_CLASS(Float, float, float, ValueKind::Float, as_float,
|
||||
BinaryWriter& write(BinaryWriter& w) const override {
|
||||
return w << this->_value();
|
||||
}
|
||||
boost::optional<double> as_double() const override {
|
||||
return this->value;
|
||||
});
|
||||
PRESERVES_ATOMIC_VALUE_CLASS(Double, double, double, ValueKind::Double, as_double,
|
||||
BinaryWriter& write(BinaryWriter& w) const override {
|
||||
return w << this->_value();
|
||||
|
@ -57,13 +50,6 @@ namespace Preserves {
|
|||
return boost::none;
|
||||
}
|
||||
}
|
||||
boost::optional<float> as_float() const override {
|
||||
if (uint64_t(float(this->value)) == this->value) {
|
||||
return float(this->value);
|
||||
} else {
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
boost::optional<double> as_double() const override {
|
||||
if (uint64_t(double(this->value)) == this->value) {
|
||||
return double(this->value);
|
||||
|
@ -82,13 +68,6 @@ namespace Preserves {
|
|||
return boost::none;
|
||||
}
|
||||
}
|
||||
boost::optional<float> as_float() const override {
|
||||
if (int64_t(float(this->value)) == this->value) {
|
||||
return float(this->value);
|
||||
} else {
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
boost::optional<double> as_double() const override {
|
||||
if (int64_t(double(this->value)) == this->value) {
|
||||
return double(this->value);
|
||||
|
@ -295,7 +274,6 @@ namespace Preserves {
|
|||
bool is_mutable() const override { return underlying.is_mutable(); }
|
||||
|
||||
boost::optional<bool> as_bool() const override { return underlying.as_bool(); }
|
||||
boost::optional<float> as_float() const override { return underlying.as_float(); }
|
||||
boost::optional<double> as_double() const override { return underlying.as_double(); }
|
||||
boost::optional<uint64_t> as_unsigned() const override { return underlying.as_unsigned(); }
|
||||
boost::optional<int64_t> as_signed() const override { return underlying.as_signed(); }
|
||||
|
@ -333,8 +311,7 @@ namespace Preserves {
|
|||
}
|
||||
|
||||
BinaryWriter& write(BinaryWriter &w) const override {
|
||||
for (auto& v : anns) { w << BinaryTag::Annotation << v; }
|
||||
return w << underlying;
|
||||
return w.annotated(underlying, anns);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -356,12 +333,6 @@ namespace Preserves {
|
|||
return Value<T>(new Boolean<T>(b));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Value<T> Value<T>::from_float(float f)
|
||||
{
|
||||
return Value<T>(new Float<T>(f));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Value<T> Value<T>::from_double(double d)
|
||||
{
|
||||
|
|
|
@ -14,7 +14,6 @@ namespace Preserves {
|
|||
|
||||
enum class ValueKind {
|
||||
Boolean,
|
||||
Float,
|
||||
Double,
|
||||
SignedInteger,
|
||||
String,
|
||||
|
@ -43,7 +42,6 @@ namespace Preserves {
|
|||
std::shared_ptr<ValueImpl<T>> _impl() const { return p; }
|
||||
|
||||
static Value from_bool(bool b);
|
||||
static Value from_float(float f);
|
||||
static Value from_double(double d);
|
||||
static Value from_int(uint64_t i);
|
||||
static Value from_int(int64_t i);
|
||||
|
@ -67,11 +65,9 @@ namespace Preserves {
|
|||
|
||||
static Value from_number(uint64_t i) { return from_int(i); }
|
||||
static Value from_number(int64_t i) { return from_int(i); }
|
||||
static Value from_number(float f) { return from_float(f); }
|
||||
static Value from_number(double d) { return from_double(d); }
|
||||
|
||||
static Value from(bool b) { return from_bool(b); }
|
||||
static Value from(float f) { return from_float(f); }
|
||||
static Value from(double d) { return from_double(d); }
|
||||
static Value from(uint64_t i) { return from_int(i); }
|
||||
static Value from(unsigned i) { return from_int(uint64_t(i)); }
|
||||
|
@ -95,7 +91,6 @@ namespace Preserves {
|
|||
bool is_mutable() const;
|
||||
|
||||
bool is_bool() const { return value_kind() == ValueKind::Boolean; }
|
||||
bool is_float() const { return value_kind() == ValueKind::Float; }
|
||||
bool is_double() const { return value_kind() == ValueKind::Double; }
|
||||
bool is_int() const { return value_kind() == ValueKind::SignedInteger; }
|
||||
bool is_string() const { return value_kind() == ValueKind::String; }
|
||||
|
@ -109,9 +104,6 @@ namespace Preserves {
|
|||
boost::optional<bool> as_bool() const;
|
||||
bool to_bool() const { return as_bool().value(); }
|
||||
|
||||
boost::optional<float> as_float() const;
|
||||
float to_float() const { return as_float().value(); }
|
||||
|
||||
boost::optional<double> as_double() const;
|
||||
double to_double() const { return as_double().value(); }
|
||||
|
||||
|
@ -175,7 +167,6 @@ namespace Preserves {
|
|||
virtual bool is_mutable() const { return false; }
|
||||
|
||||
virtual boost::optional<bool> as_bool() const { return boost::none; }
|
||||
virtual boost::optional<float> as_float() const { return boost::none; }
|
||||
virtual boost::optional<double> as_double() const { return boost::none; }
|
||||
virtual boost::optional<uint64_t> as_unsigned() const { return boost::none; }
|
||||
virtual boost::optional<int64_t> as_signed() const { return boost::none; }
|
||||
|
@ -219,7 +210,6 @@ namespace Preserves {
|
|||
#define PRESERVES_DELEGATE_CAST(t, name) \
|
||||
template <typename T> boost::optional<t> Value<T>::name() const { return p->name(); }
|
||||
PRESERVES_DELEGATE_CAST(bool, as_bool);
|
||||
PRESERVES_DELEGATE_CAST(float, as_float);
|
||||
PRESERVES_DELEGATE_CAST(double, as_double);
|
||||
PRESERVES_DELEGATE_CAST(uint64_t, as_unsigned);
|
||||
PRESERVES_DELEGATE_CAST(int64_t, as_signed);
|
||||
|
@ -265,7 +255,6 @@ namespace Preserves {
|
|||
if (bKind < aKind) return false;
|
||||
switch (aKind) {
|
||||
case ValueKind::Boolean: return a.to_bool() < b.to_bool();
|
||||
case ValueKind::Float: return a.to_float() < b.to_float();
|
||||
case ValueKind::Double: return a.to_double() < b.to_double();
|
||||
case ValueKind::SignedInteger: {
|
||||
if (auto av = a.as_signed()) {
|
||||
|
|
|
@ -41,7 +41,7 @@ let render
|
|||
)
|
||||
m
|
||||
++ " }"
|
||||
, embedded = λ(value : Text) → "#!${value}"
|
||||
, embedded = λ(value : Text) → "#:${value}"
|
||||
}
|
||||
|
||||
let Preserves/boolean = ./boolean.dhall
|
||||
|
@ -94,7 +94,7 @@ let example0 =
|
|||
)}
|
||||
''
|
||||
≡ ''
|
||||
{ a: 1 b: [ 2 3 ] c: { d: 1.0 e: -1.0 } d: #!#t e: <capture <_>> }
|
||||
{ a: 1 b: [ 2 3 ] c: { d: 1.0 e: -1.0 } d: #:#t e: <capture <_>> }
|
||||
''
|
||||
|
||||
in render
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
"devDependencies": {
|
||||
"@rollup/plugin-terser": "^0.4",
|
||||
"@types/jest": "^27.0",
|
||||
"@types/node": "^20.8.5",
|
||||
"jest": "^27.4",
|
||||
"lerna": "^4.0",
|
||||
"rollup": "^3.10",
|
||||
"ts-jest": "^27.0",
|
||||
"ts-node-dev": "^1.1",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^4.9",
|
||||
"typescript-language-server": "^3.0"
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@preserves/core",
|
||||
"version": "0.22.1",
|
||||
"version": "0.995.206",
|
||||
"description": "Preserves data serialization format",
|
||||
"homepage": "https://gitlab.com/preserves/preserves",
|
||||
"license": "Apache-2.0",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Tag } from "./constants";
|
||||
import { is, isAnnotated, IsPreservesAnnotated } from "./is";
|
||||
import type { GenericEmbedded } from "./embedded";
|
||||
import type { Embeddable, GenericEmbedded } from "./embedded";
|
||||
import type { Value } from "./values";
|
||||
import type { Encoder, Preservable } from "./encoder";
|
||||
import type { Writer, PreserveWritable } from "./writer";
|
||||
|
@ -52,7 +52,7 @@ export function formatPosition(p: Position | null | string): string {
|
|||
}
|
||||
}
|
||||
|
||||
export class Annotated<T = GenericEmbedded> implements Preservable<T>, PreserveWritable<T> {
|
||||
export class Annotated<T extends Embeddable = GenericEmbedded> implements Preservable<T>, PreserveWritable<T> {
|
||||
readonly annotations: Array<Value<T>>;
|
||||
readonly pos: Position | null;
|
||||
readonly item: Value<T>;
|
||||
|
@ -67,7 +67,7 @@ export class Annotated<T = GenericEmbedded> implements Preservable<T>, PreserveW
|
|||
return this;
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | Annotated<T> {
|
||||
static __from_preserve__<T extends Embeddable>(v: Value<T>): undefined | Annotated<T> {
|
||||
return isAnnotated<T>(v) ? v : void 0;
|
||||
}
|
||||
|
||||
|
@ -109,21 +109,24 @@ export class Annotated<T = GenericEmbedded> implements Preservable<T>, PreserveW
|
|||
return true;
|
||||
}
|
||||
|
||||
static isAnnotated<T = GenericEmbedded>(x: any): x is Annotated<T> {
|
||||
static isAnnotated<T extends Embeddable = GenericEmbedded>(x: any): x is Annotated<T> {
|
||||
return isAnnotated(x);
|
||||
}
|
||||
}
|
||||
|
||||
export function annotate<T = GenericEmbedded>(v0: Value<T>, ...anns: Value<T>[]): Annotated<T> {
|
||||
export function annotate<T extends Embeddable = GenericEmbedded>(
|
||||
v0: Value<T>,
|
||||
...anns: Value<T>[]
|
||||
): Annotated<T> {
|
||||
const v = Annotated.isAnnotated<T>(v0) ? v0 : new Annotated(v0);
|
||||
anns.forEach((a) => v.annotations.push(a));
|
||||
return v;
|
||||
}
|
||||
|
||||
export function annotations<T = GenericEmbedded>(v: Value<T>): Array<Value<T>> {
|
||||
export function annotations<T extends Embeddable = GenericEmbedded>(v: Value<T>): Array<Value<T>> {
|
||||
return Annotated.isAnnotated<T>(v) ? v.annotations : [];
|
||||
}
|
||||
|
||||
export function position<T = GenericEmbedded>(v: Value<T>): Position | null {
|
||||
export function position<T extends Embeddable = GenericEmbedded>(v: Value<T>): Position | null {
|
||||
return Annotated.isAnnotated<T>(v) ? v.pos : null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
const BASE64_DEC: {[key: string]: number} = {};
|
||||
[... 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'].forEach(
|
||||
(c, i) => BASE64_DEC[c] = i);
|
||||
BASE64_DEC['+'] = BASE64_DEC['-'] = 62;
|
||||
BASE64_DEC['/'] = BASE64_DEC['_'] = 63;
|
||||
|
||||
export function decodeBase64(s: string): Uint8Array {
|
||||
const bs = new Uint8Array(Math.floor(s.length * 3/4));
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
while (i < s.length) {
|
||||
const v1 = BASE64_DEC[s[i++]];
|
||||
const v2 = BASE64_DEC[s[i++]];
|
||||
const v3 = BASE64_DEC[s[i++]];
|
||||
const v4 = BASE64_DEC[s[i++]];
|
||||
const v = (v1 << 18) | (v2 << 12) | (v3 << 6) | v4;
|
||||
bs[j++] = (v >> 16) & 255;
|
||||
if (v3 === void 0) break;
|
||||
bs[j++] = (v >> 8) & 255;
|
||||
if (v4 === void 0) break;
|
||||
bs[j++] = v & 255;
|
||||
}
|
||||
return bs.subarray(0, j);
|
||||
}
|
||||
|
||||
const BASE64_ENC = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
|
||||
export function encodeBase64(bs: Uint8Array): string {
|
||||
let s = '';
|
||||
let buffer = 0;
|
||||
let bitcount = 0;
|
||||
for (let b of bs) {
|
||||
buffer = ((buffer & 0x3f) << 8) | b;
|
||||
bitcount += 8;
|
||||
while (bitcount >= 6) {
|
||||
bitcount -= 6;
|
||||
const v = (buffer >> bitcount) & 0x3f;
|
||||
s = s + BASE64_ENC[v];
|
||||
}
|
||||
}
|
||||
if (bitcount > 0) {
|
||||
const v = (buffer << (6 - bitcount)) & 0x3f;
|
||||
s = s + BASE64_ENC[v];
|
||||
}
|
||||
return s;
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
import { Tag } from './constants';
|
||||
import { GenericEmbedded } from './embedded';
|
||||
import type { Embeddable, GenericEmbedded } from './embedded';
|
||||
import { Encoder, Preservable } from './encoder';
|
||||
import { Value } from './values';
|
||||
import { Writer, PreserveWritable } from './writer';
|
||||
import type { Writer, PreserveWritable } from './writer';
|
||||
import { decodeBase64, encodeBase64 } from './base64';
|
||||
|
||||
const textEncoder = new TextEncoder();
|
||||
const textDecoder = new TextDecoder();
|
||||
const textDecoder = new TextDecoder('utf-8', { fatal: true });
|
||||
|
||||
export const IsPreservesBytes = Symbol.for('IsPreservesBytes');
|
||||
|
||||
|
@ -51,17 +52,37 @@ export class Bytes implements Preservable<any>, PreserveWritable<any> {
|
|||
return new Bytes(Uint8Array.of(...bytes));
|
||||
}
|
||||
|
||||
static fromHex(s: string): Bytes {
|
||||
if (s.length & 1) throw new Error("Cannot decode odd-length hexadecimal string");
|
||||
const len = s.length >> 1;
|
||||
const result = new Bytes(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
result._view[i] =
|
||||
(unhexDigit(s.charCodeAt(i << 1)) << 4) | unhexDigit(s.charCodeAt((i << 1) + 1));
|
||||
static fromLatin1(s: string): Bytes {
|
||||
// Takes codepoints in [0..255] from s, treats them as bytes.
|
||||
// Codepoints outside that range trigger an exception.
|
||||
const result = new Bytes(s.length); // assume all the codepoints are OK
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
const n = s.charCodeAt(i);
|
||||
if (n >= 256) throw new Error("Codepoint out of range for 'latin1' byte encoding");
|
||||
result._view[i] = n;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static fromBase64(s: string): Bytes {
|
||||
return new Bytes(decodeBase64(s));
|
||||
}
|
||||
|
||||
static fromHex(s: string): Bytes {
|
||||
if (s.length & 1) throw new Error("Cannot decode odd-length hexadecimal string");
|
||||
const result = new Bytes(s.length >> 1);
|
||||
Bytes._raw_fromHexInto(s, result._view);
|
||||
return result;
|
||||
}
|
||||
|
||||
static _raw_fromHexInto(s: string, target: Uint8Array): void {
|
||||
const len = s.length >> 1;
|
||||
for (let i = 0; i < len; i++) {
|
||||
target[i] =
|
||||
(unhexDigit(s.charCodeAt(i << 1)) << 4) | unhexDigit(s.charCodeAt((i << 1) + 1));
|
||||
}
|
||||
}
|
||||
|
||||
static fromIO(io: string | BytesLike): string | Bytes {
|
||||
if (typeof io === 'string') return io;
|
||||
if (Bytes.isBytes(io)) return io;
|
||||
|
@ -127,19 +148,27 @@ export class Bytes implements Preservable<any>, PreserveWritable<any> {
|
|||
return textDecoder.decode(this._view);
|
||||
}
|
||||
|
||||
__as_preserve__<T = GenericEmbedded>(): Value<T> {
|
||||
__as_preserve__<T extends Embeddable = GenericEmbedded>(): Value<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | Bytes {
|
||||
static __from_preserve__<T extends Embeddable>(v: Value<T>): undefined | Bytes {
|
||||
return Bytes.isBytes(v) ? v : void 0;
|
||||
}
|
||||
|
||||
toHex(): string {
|
||||
toLatin1(): string {
|
||||
return String.fromCharCode.apply(null, this._view as any as number[]);
|
||||
}
|
||||
|
||||
toBase64(): string {
|
||||
return encodeBase64(this._view);
|
||||
}
|
||||
|
||||
toHex(digit = hexDigit): string {
|
||||
var nibbles = [];
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
nibbles.push(hexDigit(this._view[i] >> 4));
|
||||
nibbles.push(hexDigit(this._view[i] & 15));
|
||||
nibbles.push(digit(this._view[i] >> 4));
|
||||
nibbles.push(digit(this._view[i] & 15));
|
||||
}
|
||||
return nibbles.join('');
|
||||
}
|
||||
|
@ -172,11 +201,11 @@ export function hexDigit(n: number): string {
|
|||
return '0123456789abcdef'[n];
|
||||
}
|
||||
|
||||
export function unhexDigit(asciiCode: number) {
|
||||
export function unhexDigit(asciiCode: number, errorClass: {new(msg: string): Error} = Error) {
|
||||
if (asciiCode >= 48 && asciiCode <= 57) return asciiCode - 48;
|
||||
if (asciiCode >= 97 && asciiCode <= 102) return asciiCode - 97 + 10;
|
||||
if (asciiCode >= 65 && asciiCode <= 70) return asciiCode - 65 + 10;
|
||||
throw new Error("Invalid hex digit: " + String.fromCharCode(asciiCode));
|
||||
throw new errorClass("Invalid hex digit: " + String.fromCharCode(asciiCode));
|
||||
}
|
||||
|
||||
export function underlying(b: Bytes | Uint8Array): Uint8Array {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import type { Compound, Value } from "./values";
|
||||
import type { GenericEmbedded } from "./embedded";
|
||||
import type { Embeddable, GenericEmbedded } from "./embedded";
|
||||
import { Dictionary, Set } from "./dictionary";
|
||||
|
||||
export function isCompound<T = GenericEmbedded>(x: Value<T>): x is Compound<T>
|
||||
export function isCompound<T extends Embeddable = GenericEmbedded>(x: Value<T>): x is Compound<T>
|
||||
{
|
||||
return (Array.isArray(x) || Set.isSet(x) || Dictionary.isDictionary(x));
|
||||
}
|
||||
|
||||
export function isSequence<T = GenericEmbedded>(x: Value<T>): x is Array<Value<T>> {
|
||||
export function isSequence<T extends Embeddable = GenericEmbedded>(x: Value<T>): x is Array<Value<T>> {
|
||||
return (Array.isArray(x) && !('label' in x));
|
||||
}
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
export enum Tag {
|
||||
False = 0x80,
|
||||
True,
|
||||
Float,
|
||||
Double,
|
||||
End,
|
||||
End = 0x84,
|
||||
Annotation,
|
||||
Embedded,
|
||||
|
||||
SmallInteger_lo = 0x90,
|
||||
MediumInteger_lo = 0xa0,
|
||||
Ieee754,
|
||||
|
||||
SignedInteger = 0xb0,
|
||||
String,
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
import { Annotated } from "./annotated";
|
||||
import { DecodeError, ShortPacket } from "./codec";
|
||||
import { Tag } from "./constants";
|
||||
import { Set, Dictionary } from "./dictionary";
|
||||
import { DoubleFloat, SingleFloat } from "./float";
|
||||
import { Set, Dictionary, DictionaryMap } from "./dictionary";
|
||||
import { DoubleFloat } from "./float";
|
||||
import { Record } from "./record";
|
||||
import { Bytes, BytesLike, underlying } from "./bytes";
|
||||
import { Bytes, BytesLike, underlying, hexDigit } from "./bytes";
|
||||
import { Value } from "./values";
|
||||
import { is } from "./is";
|
||||
import { embed, GenericEmbedded, Embedded, EmbeddedTypeDecode } from "./embedded";
|
||||
import { ReaderStateOptions } from "reader";
|
||||
import { GenericEmbedded, Embeddable, EmbeddedTypeDecode } from "./embedded";
|
||||
import { ReaderStateOptions } from "./reader";
|
||||
import { stringify } from "./text";
|
||||
|
||||
export interface DecoderOptions {
|
||||
includeAnnotations?: boolean;
|
||||
}
|
||||
|
||||
export interface DecoderEmbeddedOptions<T> extends DecoderOptions {
|
||||
export interface DecoderEmbeddedOptions<T extends Embeddable> extends DecoderOptions {
|
||||
embeddedDecode?: EmbeddedTypeDecode<T>;
|
||||
}
|
||||
|
||||
export interface TypedDecoder<T> {
|
||||
export interface TypedDecoder<T extends Embeddable> {
|
||||
atEnd(): boolean;
|
||||
|
||||
mark(): any;
|
||||
restoreMark(m: any): void;
|
||||
restoreMark(m: any): undefined;
|
||||
|
||||
skip(): void;
|
||||
next(): Value<T>;
|
||||
withEmbeddedDecode<S, R>(
|
||||
withEmbeddedDecode<S extends Embeddable, R>(
|
||||
embeddedDecode: EmbeddedTypeDecode<S>,
|
||||
body: (d: TypedDecoder<S>) => R): R;
|
||||
|
||||
nextBoolean(): boolean | undefined;
|
||||
nextFloat(): SingleFloat | undefined;
|
||||
nextDouble(): DoubleFloat | undefined;
|
||||
nextEmbedded(): Embedded<T> | undefined;
|
||||
nextSignedInteger(): number | undefined;
|
||||
nextEmbedded(): T | undefined;
|
||||
nextSignedInteger(): number | bigint | undefined;
|
||||
nextString(): string | undefined;
|
||||
nextByteString(): Bytes | undefined;
|
||||
nextSymbol(): symbol | undefined;
|
||||
|
@ -47,7 +47,7 @@ export interface TypedDecoder<T> {
|
|||
closeCompound(): boolean;
|
||||
}
|
||||
|
||||
export function asLiteral<T, E extends Exclude<Value<T>, Annotated<T>>>(
|
||||
export function asLiteral<T extends Embeddable, E extends Exclude<Value<T>, Annotated<T>>>(
|
||||
actual: Value<T>,
|
||||
expected: E): E | undefined
|
||||
{
|
||||
|
@ -85,8 +85,9 @@ export class DecoderState {
|
|||
return this.index;
|
||||
}
|
||||
|
||||
restoreMark(m: number): void {
|
||||
restoreMark(m: number): undefined {
|
||||
this.index = m;
|
||||
return void 0;
|
||||
}
|
||||
|
||||
shortGuard<R>(body: () => R, short: () => R): R {
|
||||
|
@ -129,32 +130,47 @@ export class DecoderState {
|
|||
return (this.nextbyte() === Tag.End) || (this.index--, false);
|
||||
}
|
||||
|
||||
nextint(n: number): number {
|
||||
// TODO: Bignums :-/
|
||||
nextint(n: number): number | bigint {
|
||||
const start = this.index;
|
||||
if (n === 0) return 0;
|
||||
if (n > 7) return this.nextbigint(n);
|
||||
if (n === 7) {
|
||||
const highByte = this.packet[this.index];
|
||||
if ((highByte >= 0x20) && (highByte < 0xe0)) {
|
||||
return this.nextbigint(n);
|
||||
}
|
||||
// if highByte is 0xe0, we still might have a value
|
||||
// equal to (Number.MIN_SAFE_INTEGER-1).
|
||||
}
|
||||
let acc = this.nextbyte();
|
||||
if (acc & 0x80) acc -= 256;
|
||||
for (let i = 1; i < n; i++) acc = (acc * 256) + this.nextbyte();
|
||||
if (!Number.isSafeInteger(acc)) {
|
||||
this.index = start;
|
||||
return this.nextbigint(n);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
nextSmallOrMediumInteger(tag: number): number | undefined {
|
||||
if (tag >= Tag.SmallInteger_lo && tag <= Tag.SmallInteger_lo + 15) {
|
||||
const v = tag - Tag.SmallInteger_lo;
|
||||
return v > 12 ? v - 16 : v;
|
||||
nextbigint(n: number): bigint {
|
||||
if (n === 0) return BigInt(0);
|
||||
const bs = Bytes.from(this.nextbytes(n));
|
||||
if (bs.get(0) >= 128) {
|
||||
// negative
|
||||
const hex = bs.toHex(d => hexDigit(15 - d));
|
||||
return ~BigInt('0x' + hex);
|
||||
} else {
|
||||
// (strictly) positive
|
||||
const hex = bs.toHex();
|
||||
return BigInt('0x' + hex);
|
||||
}
|
||||
if (tag >= Tag.MediumInteger_lo && tag <= Tag.MediumInteger_lo + 15) {
|
||||
const n = tag - Tag.MediumInteger_lo;
|
||||
return this.nextint(n + 1);
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
wrap<T>(v: Value<T>): Value<T> {
|
||||
wrap<T extends Embeddable>(v: Value<T>): Value<T> {
|
||||
return this.includeAnnotations ? new Annotated(v) : v;
|
||||
}
|
||||
|
||||
unshiftAnnotation<T>(a: Value<T>, v: Annotated<T>): Annotated<T> {
|
||||
unshiftAnnotation<T extends Embeddable>(a: Value<T>, v: Annotated<T>): Annotated<T> {
|
||||
if (this.includeAnnotations) {
|
||||
v.annotations.unshift(a);
|
||||
}
|
||||
|
@ -172,7 +188,7 @@ export const neverEmbeddedTypeDecode: EmbeddedTypeDecode<never> = {
|
|||
},
|
||||
};
|
||||
|
||||
export class Decoder<T = never> implements TypedDecoder<T> {
|
||||
export class Decoder<T extends Embeddable = never> implements TypedDecoder<T> {
|
||||
state: DecoderState;
|
||||
embeddedDecode: EmbeddedTypeDecode<T>;
|
||||
|
||||
|
@ -202,13 +218,14 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
return result;
|
||||
}
|
||||
|
||||
static dictionaryFromArray<T>(vs: Value<T>[]): Dictionary<T> {
|
||||
const d = new Dictionary<T>();
|
||||
static dictionaryFromArray<T extends Embeddable>(vs: Value<T>[]): Dictionary<T> {
|
||||
const d = new DictionaryMap<T>();
|
||||
if (vs.length % 2) throw new DecodeError("Missing dictionary value");
|
||||
for (let i = 0; i < vs.length; i += 2) {
|
||||
if (d.has(vs[i])) throw new DecodeError(`Duplicate key: ${stringify(vs[i])}`);
|
||||
d.set(vs[i], vs[i+1]);
|
||||
}
|
||||
return d;
|
||||
return d.simplifiedValue();
|
||||
}
|
||||
|
||||
next(): Value<T> {
|
||||
|
@ -216,15 +233,18 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
switch (tag) {
|
||||
case Tag.False: return this.state.wrap<T>(false);
|
||||
case Tag.True: return this.state.wrap<T>(true);
|
||||
case Tag.Float: return this.state.wrap<T>(SingleFloat.fromBytes(this.state.nextbytes(4)));
|
||||
case Tag.Double: return this.state.wrap<T>(DoubleFloat.fromBytes(this.state.nextbytes(8)));
|
||||
case Tag.End: throw new DecodeError("Unexpected Compound end marker");
|
||||
case Tag.Annotation: {
|
||||
const a = this.next();
|
||||
const v = this.next() as Annotated<T>;
|
||||
return this.state.unshiftAnnotation(a, v);
|
||||
}
|
||||
case Tag.Embedded: return this.state.wrap<T>(embed(this.embeddedDecode.decode(this.state)));
|
||||
case Tag.Embedded: return this.state.wrap<T>(this.embeddedDecode.decode(this.state));
|
||||
case Tag.Ieee754:
|
||||
switch (this.state.varint()) {
|
||||
case 8: return this.state.wrap<T>(DoubleFloat.fromBytes(this.state.nextbytes(8)));
|
||||
default: throw new DecodeError("Invalid IEEE754 size");
|
||||
}
|
||||
case Tag.SignedInteger: return this.state.wrap<T>(this.state.nextint(this.state.varint()));
|
||||
case Tag.String: return this.state.wrap<T>(Bytes.from(this.state.nextbytes(this.state.varint())).fromUtf8());
|
||||
case Tag.ByteString: return this.state.wrap<T>(Bytes.from(this.state.nextbytes(this.state.varint())));
|
||||
|
@ -235,15 +255,16 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
return this.state.wrap<T>(Record(vs[0], vs.slice(1)));
|
||||
}
|
||||
case Tag.Sequence: return this.state.wrap<T>(this.nextvalues());
|
||||
case Tag.Set: return this.state.wrap<T>(new Set(this.nextvalues()));
|
||||
case Tag.Dictionary: return this.state.wrap<T>(Decoder.dictionaryFromArray(this.nextvalues()));
|
||||
default: {
|
||||
const v = this.state.nextSmallOrMediumInteger(tag);
|
||||
if (v === void 0) {
|
||||
throw new DecodeError("Unsupported Preserves tag: " + tag);
|
||||
case Tag.Set: {
|
||||
const s = new Set<T>();
|
||||
for (const v of this.nextvalues()) {
|
||||
if (s.has(v)) throw new DecodeError(`Duplicate value: ${stringify(v)}`);
|
||||
s.add(v);
|
||||
}
|
||||
return this.state.wrap<T>(v);
|
||||
return this.state.wrap<T>(s);
|
||||
}
|
||||
case Tag.Dictionary: return this.state.wrap<T>(Decoder.dictionaryFromArray(this.nextvalues()));
|
||||
default: throw new DecodeError("Unsupported Preserves tag: " + tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,8 +280,8 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
return this.state.mark();
|
||||
}
|
||||
|
||||
restoreMark(m: any): void {
|
||||
this.state.restoreMark(m);
|
||||
restoreMark(m: any): undefined {
|
||||
return this.state.restoreMark(m);
|
||||
}
|
||||
|
||||
skip(): void {
|
||||
|
@ -268,106 +289,105 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
this.next();
|
||||
}
|
||||
|
||||
withEmbeddedDecode<S, R>(
|
||||
withEmbeddedDecode<S extends Embeddable, R>(
|
||||
embeddedDecode: EmbeddedTypeDecode<S>,
|
||||
body: (d: TypedDecoder<S>) => R): R
|
||||
{
|
||||
return body(new Decoder(this.state, embeddedDecode));
|
||||
}
|
||||
|
||||
skipAnnotations(): void {
|
||||
if (!this.state.atEnd() && this.state.packet[this.state.index] === Tag.Annotation) {
|
||||
skipAnnotations<R>(f: (reset: () => undefined) => R): R {
|
||||
const m = this.mark();
|
||||
while (!this.state.atEnd() && this.state.packet[this.state.index] === Tag.Annotation) {
|
||||
this.state.index++;
|
||||
this.skip();
|
||||
}
|
||||
return f(() => this.restoreMark(m));
|
||||
}
|
||||
|
||||
nextBoolean(): boolean | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.False: return false;
|
||||
case Tag.True: return true;
|
||||
default: return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
nextFloat(): SingleFloat | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.Float: return SingleFloat.fromBytes(this.state.nextbytes(4));
|
||||
default: return void 0;
|
||||
}
|
||||
return this.skipAnnotations((reset) => {
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.False: return false;
|
||||
case Tag.True: return true;
|
||||
default: return reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextDouble(): DoubleFloat | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.Double: return DoubleFloat.fromBytes(this.state.nextbytes(8));
|
||||
default: return void 0;
|
||||
}
|
||||
return this.skipAnnotations((reset) => {
|
||||
if (this.state.nextbyte() !== Tag.Ieee754) return reset();
|
||||
if (this.state.nextbyte() !== 8) return reset();
|
||||
return DoubleFloat.fromBytes(this.state.nextbytes(8));
|
||||
});
|
||||
}
|
||||
|
||||
nextEmbedded(): Embedded<T> | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.Embedded: return embed(this.embeddedDecode.decode(this.state));
|
||||
default: return void 0;
|
||||
}
|
||||
nextEmbedded(): T | undefined {
|
||||
return this.skipAnnotations((reset) => {
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.Embedded: return this.embeddedDecode.decode(this.state);
|
||||
default: return reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextSignedInteger(): number | undefined {
|
||||
this.skipAnnotations();
|
||||
const b = this.state.nextbyte();
|
||||
switch (b) {
|
||||
case Tag.SignedInteger: return this.state.nextint(this.state.varint());
|
||||
default: return this.state.nextSmallOrMediumInteger(b);
|
||||
}
|
||||
nextSignedInteger(): number | bigint | undefined {
|
||||
return this.skipAnnotations((reset) => {
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.SignedInteger: return this.state.nextint(this.state.varint());
|
||||
default: return reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextString(): string | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.String: return Bytes.from(this.state.nextbytes(this.state.varint())).fromUtf8();
|
||||
default: return void 0;
|
||||
}
|
||||
return this.skipAnnotations((reset) => {
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.String: return Bytes.from(this.state.nextbytes(this.state.varint())).fromUtf8();
|
||||
default: return reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextByteString(): Bytes | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.ByteString: return Bytes.from(this.state.nextbytes(this.state.varint()));
|
||||
default: return void 0;
|
||||
}
|
||||
return this.skipAnnotations((reset) => {
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.ByteString: return Bytes.from(this.state.nextbytes(this.state.varint()));
|
||||
default: return reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextSymbol(): symbol | undefined {
|
||||
this.skipAnnotations();
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.Symbol:
|
||||
return Symbol.for(Bytes.from(this.state.nextbytes(this.state.varint())).fromUtf8());
|
||||
default:
|
||||
return void 0;
|
||||
}
|
||||
return this.skipAnnotations((reset) => {
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.Symbol:
|
||||
return Symbol.for(Bytes.from(this.state.nextbytes(this.state.varint())).fromUtf8());
|
||||
default:
|
||||
return reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openRecord(): boolean {
|
||||
this.skipAnnotations();
|
||||
return (this.state.nextbyte() === Tag.Record) || (this.state.index--, false);
|
||||
return this.skipAnnotations((reset) =>
|
||||
(this.state.nextbyte() === Tag.Record) || (reset(), false));
|
||||
}
|
||||
|
||||
openSequence(): boolean {
|
||||
this.skipAnnotations();
|
||||
return (this.state.nextbyte() === Tag.Sequence) || (this.state.index--, false);
|
||||
return this.skipAnnotations((reset) =>
|
||||
(this.state.nextbyte() === Tag.Sequence) || (reset(), false));
|
||||
}
|
||||
|
||||
openSet(): boolean {
|
||||
this.skipAnnotations();
|
||||
return (this.state.nextbyte() === Tag.Set) || (this.state.index--, false);
|
||||
return this.skipAnnotations((reset) =>
|
||||
(this.state.nextbyte() === Tag.Set) || (reset(), false));
|
||||
}
|
||||
|
||||
openDictionary(): boolean {
|
||||
this.skipAnnotations();
|
||||
return (this.state.nextbyte() === Tag.Dictionary) || (this.state.index--, false);
|
||||
return this.skipAnnotations((reset) =>
|
||||
(this.state.nextbyte() === Tag.Dictionary) || (reset(), false));
|
||||
}
|
||||
|
||||
closeCompound(): boolean {
|
||||
|
@ -375,11 +395,16 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
}
|
||||
}
|
||||
|
||||
export function decode<T>(bs: BytesLike, options: DecoderEmbeddedOptions<T> = {}): Value<T> {
|
||||
export function decode<T extends Embeddable>(
|
||||
bs: BytesLike,
|
||||
options: DecoderEmbeddedOptions<T> = {},
|
||||
): Value<T> {
|
||||
return new Decoder(bs, options).next();
|
||||
}
|
||||
|
||||
export function decodeWithAnnotations<T>(bs: BytesLike,
|
||||
options: DecoderEmbeddedOptions<T> = {}): Annotated<T> {
|
||||
export function decodeWithAnnotations<T extends Embeddable>(
|
||||
bs: BytesLike,
|
||||
options: DecoderEmbeddedOptions<T> = {},
|
||||
): Annotated<T> {
|
||||
return decode(bs, { ... options, includeAnnotations: true }) as Annotated<T>;
|
||||
}
|
||||
|
|
|
@ -1,149 +1,404 @@
|
|||
import { Encoder, canonicalEncode, canonicalString } from "./encoder";
|
||||
import { Encoder, canonicalString } from "./encoder";
|
||||
import { Tag } from "./constants";
|
||||
import { FlexMap, FlexSet, _iterMap } from "./flex";
|
||||
import { FlexMap, FlexSet, _iterMap, IdentitySet, Equivalence, IsMap } from "./flex";
|
||||
import { Value } from "./values";
|
||||
import { Bytes } from './bytes';
|
||||
import { GenericEmbedded } from "./embedded";
|
||||
import { Embeddable, GenericEmbedded, isEmbedded } from "./embedded";
|
||||
import type { Preservable } from "./encoder";
|
||||
import type { Writer, PreserveWritable } from "./writer";
|
||||
import { annotations, Annotated } from "./annotated";
|
||||
import { Float } from "./float";
|
||||
import { JsDictionary } from "./jsdictionary";
|
||||
import { unannotate } from "./strip";
|
||||
|
||||
export type DictionaryType = 'Dictionary' | 'Set';
|
||||
export const DictionaryType = Symbol.for('DictionaryType');
|
||||
|
||||
export class KeyedDictionary<K extends Value<T>, V, T = GenericEmbedded> extends FlexMap<K, V>
|
||||
export type CompoundKey<T extends Embeddable> = Value<T> | (Preservable<T> & PreserveWritable<T>);
|
||||
|
||||
export class EncodableDictionary<T extends Embeddable, K, V> extends FlexMap<K, V>
|
||||
implements Preservable<T>, PreserveWritable<T>
|
||||
{
|
||||
constructor(
|
||||
public readonly encodeK: (k: K) => CompoundKey<T>,
|
||||
public readonly encodeV: (v: V) => CompoundKey<T>,
|
||||
items?: readonly [K, V][] | Iterable<readonly [K, V]>
|
||||
) {
|
||||
super((k: K) => canonicalString(encodeK(k)), items);
|
||||
}
|
||||
|
||||
__preserve_on__(encoder: Encoder<T>) {
|
||||
encodeDictionaryOn(this,
|
||||
encoder,
|
||||
(k, e) => e.push(this.encodeK(k)),
|
||||
(v, e) => e.push(this.encodeV(v)));
|
||||
}
|
||||
|
||||
__preserve_text_on__(w: Writer<T>) {
|
||||
writeDictionaryOn(this,
|
||||
w,
|
||||
(k, w) => w.push(this.encodeK(k)),
|
||||
(v, w) => w.push(this.encodeV(v)));
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyedDictionary<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>, V = Value<T>>
|
||||
extends EncodableDictionary<T, K, V>
|
||||
{
|
||||
get [DictionaryType](): DictionaryType {
|
||||
return 'Dictionary';
|
||||
}
|
||||
|
||||
static isKeyedDictionary<K extends Value<T>, V, T = GenericEmbedded>(x: any): x is KeyedDictionary<K, V, T> {
|
||||
static isKeyedDictionary<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>, V = Value<T>>(
|
||||
x: any,
|
||||
): x is KeyedDictionary<T, K, V> {
|
||||
return x?.[DictionaryType] === 'Dictionary';
|
||||
}
|
||||
|
||||
constructor(items?: readonly [K, V][]);
|
||||
constructor(items?: Iterable<readonly [K, V]>);
|
||||
constructor(items?: Iterable<readonly [K, V]>) {
|
||||
super(canonicalString, items);
|
||||
constructor(items?: readonly [K, V][] | Iterable<readonly [K, V]>) {
|
||||
// The cast in encodeV is suuuuuuuper unsound, since V may not in fact be Encodable and Writable.
|
||||
// Don't try to encode/write dictionaries holding non-encodable/non-writable values.
|
||||
super(k => k, v => v as CompoundKey<T>, items);
|
||||
}
|
||||
|
||||
mapEntries<W, S extends Value<R>, R = GenericEmbedded>(f: (entry: [K, V]) => [S, W]): KeyedDictionary<S, W, R> {
|
||||
const result = new KeyedDictionary<S, W, R>();
|
||||
for (let oldEntry of this.entries()) {
|
||||
const newEntry = f(oldEntry);
|
||||
result.set(newEntry[0], newEntry[1])
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
clone(): KeyedDictionary<K, V, T> {
|
||||
clone(): KeyedDictionary<T, K, V> {
|
||||
return new KeyedDictionary(this);
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() { return 'Dictionary'; }
|
||||
|
||||
__preserve_on__(encoder: Encoder<T>) {
|
||||
if (encoder.canonical) {
|
||||
const entries = Array.from(this);
|
||||
const pieces = entries.map<[Bytes, number]>(([k, _v], i) => [canonicalEncode(k), i]);
|
||||
pieces.sort((a, b) => Bytes.compare(a[0], b[0]));
|
||||
encoder.state.emitbyte(Tag.Dictionary);
|
||||
pieces.forEach(([_encodedKey, i]) => {
|
||||
const [k, v] = entries[i];
|
||||
encoder.push(k);
|
||||
encoder.push(v as unknown as Value<T>); // Suuuuuuuper unsound
|
||||
});
|
||||
encoder.state.emitbyte(Tag.End);
|
||||
} else {
|
||||
encoder.state.emitbyte(Tag.Dictionary);
|
||||
this.forEach((v, k) => {
|
||||
encoder.push(k);
|
||||
encoder.push(v as unknown as Value<T>); // Suuuuuuuper unsound
|
||||
});
|
||||
encoder.state.emitbyte(Tag.End);
|
||||
}
|
||||
}
|
||||
|
||||
__preserve_text_on__(w: Writer<T>) {
|
||||
w.state.writeSeq('{', '}', this.entries(), ([k, v]) => {
|
||||
w.push(k);
|
||||
if (Annotated.isAnnotated<T>(v) && (annotations(v).length > 1) && w.state.isIndenting) {
|
||||
w.state.pieces.push(':');
|
||||
w.state.indentCount++;
|
||||
w.state.writeIndent();
|
||||
w.push(v);
|
||||
w.state.indentCount--;
|
||||
} else {
|
||||
w.state.pieces.push(': ');
|
||||
w.push(v as unknown as Value<T>); // Suuuuuuuper unsound
|
||||
}
|
||||
});
|
||||
equals(otherAny: any, eqv: Equivalence<V> = (v1, v2) => v1 === v2): boolean {
|
||||
const otherMap = Dictionary.asMap(otherAny);
|
||||
if (!otherMap) return false;
|
||||
return super.equals(otherMap, eqv);
|
||||
}
|
||||
}
|
||||
|
||||
export class Dictionary<T = GenericEmbedded, V = Value<T>> extends KeyedDictionary<Value<T>, V, T> {
|
||||
static isDictionary<T = GenericEmbedded, V = Value<T>>(x: any): x is Dictionary<T, V> {
|
||||
return x?.[DictionaryType] === 'Dictionary';
|
||||
export type Dictionary<T extends Embeddable = GenericEmbedded, V = Value<T>> =
|
||||
JsDictionary<V> | KeyedDictionary<T, Value<T>, V>;
|
||||
|
||||
export class DictionaryMap<T extends Embeddable = GenericEmbedded, V = Value<T>> implements Map<Value<T>, V> {
|
||||
get [IsMap](): boolean { return true; }
|
||||
|
||||
j: JsDictionary<V> | undefined;
|
||||
k: KeyedDictionary<T, Value<T>, V> | undefined;
|
||||
|
||||
constructor(input?: Dictionary<T, V>) {
|
||||
if (input === void 0) {
|
||||
this.j = {};
|
||||
this.k = void 0;
|
||||
} else if (DictionaryType in input) {
|
||||
this.j = void 0;
|
||||
this.k = input;
|
||||
} else {
|
||||
this.j = input;
|
||||
this.k = void 0;
|
||||
}
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | Dictionary<T> {
|
||||
static from<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
||||
entries: [Value<T>, V][] | Iterable<[Value<T>, V]>,
|
||||
): DictionaryMap<T, V> {
|
||||
const r = new DictionaryMap<T, V>();
|
||||
for (const [key, value] of entries) r.set(key, value);
|
||||
return r;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
if (this.j) {
|
||||
JsDictionary.clear(this.j);
|
||||
} else {
|
||||
this.k!.clear();
|
||||
}
|
||||
}
|
||||
|
||||
delete(key: Value<T>): boolean {
|
||||
if (this.j) {
|
||||
key = unannotate(key);
|
||||
if (typeof key !== 'symbol') return false;
|
||||
return JsDictionary.remove(this.j, key);
|
||||
} else {
|
||||
return this.k!.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
forEach(callbackfn: (value: V, key: Value<T>, map: Map<Value<T>, V>) => void, thisArg?: any): void {
|
||||
if (this.j) {
|
||||
JsDictionary.forEach(this.j, (v, k) => callbackfn.call(thisArg, v, k, this));
|
||||
} else {
|
||||
this.k!.forEach(callbackfn, thisArg);
|
||||
}
|
||||
}
|
||||
|
||||
get(key: Value<T>): V | undefined {
|
||||
if (this.j) {
|
||||
key = unannotate(key);
|
||||
if (typeof key !== 'symbol') return void 0;
|
||||
return JsDictionary.get(this.j, key);
|
||||
} else {
|
||||
return this.k!.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
has(key: Value<T>): boolean {
|
||||
if (this.j) {
|
||||
key = unannotate(key);
|
||||
if (typeof key !== 'symbol') return false;
|
||||
return JsDictionary.has(this.j, key);
|
||||
} else {
|
||||
return this.k!.has(key);
|
||||
}
|
||||
}
|
||||
|
||||
set(key: Value<T>, value: V): this {
|
||||
if (this.j) {
|
||||
if (typeof key === 'symbol') {
|
||||
JsDictionary.set(this.j, key, value);
|
||||
return this;
|
||||
}
|
||||
this.k = new KeyedDictionary<T, Value<T>, V>(JsDictionary.entries(this.j));
|
||||
this.j = void 0;
|
||||
}
|
||||
this.k!.set(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.j ? JsDictionary.size(this.j) : this.k!.size;
|
||||
}
|
||||
|
||||
entries(): IterableIterator<[Value<T>, V]> {
|
||||
return this.j ? JsDictionary.entries(this.j) : this.k!.entries();
|
||||
}
|
||||
|
||||
keys(): IterableIterator<Value<T>> {
|
||||
return this.j ? JsDictionary.keys(this.j) : this.k!.keys();
|
||||
}
|
||||
|
||||
values(): IterableIterator<V> {
|
||||
return this.j ? JsDictionary.values(this.j) : this.k!.values();
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<[Value<T>, V]> {
|
||||
return this.entries();
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag](): string {
|
||||
return 'DictionaryMap';
|
||||
}
|
||||
|
||||
clone(): DictionaryMap<T, V> {
|
||||
return new DictionaryMap<T, V>(this.j ? JsDictionary.clone(this.j) : this.k!.clone());
|
||||
}
|
||||
|
||||
get value(): Dictionary<T, V> {
|
||||
return this.j ?? this.k!;
|
||||
}
|
||||
|
||||
simplify(): void {
|
||||
if (!this.j) {
|
||||
const r: JsDictionary<V> = {};
|
||||
for (const [key, value] of this.k!.entries()) {
|
||||
if (typeof key !== 'symbol') return;
|
||||
r[key.description!] = value;
|
||||
}
|
||||
this.j = r;
|
||||
this.k = void 0;
|
||||
}
|
||||
}
|
||||
|
||||
simplifiedValue(): Dictionary<T, V> {
|
||||
this.simplify();
|
||||
return this.value;
|
||||
}
|
||||
|
||||
asJsDictionary(): JsDictionary<V> {
|
||||
this.simplify();
|
||||
if (!this.j) throw new Error("Cannot represent general dictionary as JsDictionary");
|
||||
return this.j;
|
||||
}
|
||||
|
||||
asKeyedDictionary(): KeyedDictionary<T, Value<T>, V> {
|
||||
return this.k ?? new KeyedDictionary<T, Value<T>, V>(JsDictionary.entries(this.j!));
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Dictionary {
|
||||
export function isDictionary<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
||||
x: any
|
||||
): x is Dictionary<T, V> {
|
||||
if (typeof x !== 'object' || x === null) return false;
|
||||
switch (x[DictionaryType]) {
|
||||
case 'Dictionary': return true;
|
||||
case void 0: return JsDictionary.isJsDictionary(x);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function asMap<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
||||
x: Dictionary<T, V>
|
||||
): DictionaryMap<T, V>;
|
||||
export function asMap<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
||||
x: any
|
||||
): DictionaryMap<T, V> | undefined;
|
||||
export function asMap<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
||||
x: any
|
||||
): DictionaryMap<T, V> | undefined {
|
||||
return isDictionary<T, V>(x) ? new DictionaryMap(x) : void 0;
|
||||
}
|
||||
|
||||
export function from<T extends Embeddable = GenericEmbedded, V = Value<T>>(
|
||||
entries: [Value<T>, V][] | Iterable<[Value<T>, V]>,
|
||||
): Dictionary<T, V> {
|
||||
return DictionaryMap.from(entries).simplifiedValue();
|
||||
}
|
||||
|
||||
export function __from_preserve__<T extends Embeddable>(v: Value<T>): undefined | Dictionary<T> {
|
||||
return Dictionary.isDictionary<T>(v) ? v : void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyedSet<K extends Value<T>, T = GenericEmbedded> extends FlexSet<K>
|
||||
export function encodeDictionaryOn<T extends Embeddable, K, V>(
|
||||
dict: Map<K, V>,
|
||||
encoder: Encoder<T>,
|
||||
encodeK: (k: K, encoder: Encoder<T>) => void,
|
||||
encodeV: (v: V, encoder: Encoder<T>) => void,
|
||||
) {
|
||||
if (encoder.canonical) {
|
||||
const entries = Array.from(dict);
|
||||
const canonicalEncoder = new Encoder<T>({
|
||||
canonical: true,
|
||||
embeddedEncode: encoder.embeddedEncode,
|
||||
});
|
||||
const pieces = entries.map<[Bytes, number]>(([k, _v], i) => {
|
||||
encodeK(k, canonicalEncoder);
|
||||
return [canonicalEncoder.contents(), i];
|
||||
});
|
||||
pieces.sort((a, b) => Bytes.compare(a[0], b[0]));
|
||||
encoder.grouped(Tag.Dictionary, () => pieces.forEach(([_encodedKey, i]) => {
|
||||
const [k, v] = entries[i];
|
||||
encodeK(k, encoder);
|
||||
encodeV(v, encoder);
|
||||
}));
|
||||
} else {
|
||||
encoder.grouped(Tag.Dictionary, () => dict.forEach((v, k) => {
|
||||
encodeK(k, encoder);
|
||||
encodeV(v, encoder);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export function writeDictionaryOn<T extends Embeddable, K, V>(
|
||||
dict: Map<K, V>,
|
||||
w: Writer<T>,
|
||||
writeK: (k: K, w: Writer<T>) => void,
|
||||
writeV: (v: V, w: Writer<T>) => void,
|
||||
) {
|
||||
w.state.writeSeq('{', '}', dict.entries(), ([k, v]) => {
|
||||
writeK(k, w);
|
||||
if (Annotated.isAnnotated<T>(v) && (annotations(v).length > 1) && w.state.isIndenting) {
|
||||
w.state.pieces.push(':');
|
||||
w.state.indentCount++;
|
||||
w.state.writeIndent();
|
||||
writeV(v, w);
|
||||
w.state.indentCount--;
|
||||
} else {
|
||||
w.state.pieces.push(': ');
|
||||
writeV(v, w);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export class EncodableSet<T extends Embeddable, V> extends FlexSet<V>
|
||||
implements Preservable<T>, PreserveWritable<T>
|
||||
{
|
||||
constructor(
|
||||
public readonly encodeV: (v: V) => CompoundKey<T>,
|
||||
items?: Iterable<V>,
|
||||
) {
|
||||
super((v: V) => canonicalString(encodeV(v)), items);
|
||||
}
|
||||
|
||||
__preserve_on__(encoder: Encoder<T>) {
|
||||
encodeSetOn(this, encoder, (v, e) => e.push(this.encodeV(v)));
|
||||
}
|
||||
|
||||
__preserve_text_on__(w: Writer<T>) {
|
||||
writeSetOn(this, w, (v, w) => w.push(this.encodeV(v)));
|
||||
}
|
||||
}
|
||||
|
||||
export class KeyedSet<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>>
|
||||
extends EncodableSet<T, K>
|
||||
{
|
||||
get [DictionaryType](): DictionaryType {
|
||||
return 'Set';
|
||||
}
|
||||
|
||||
static isKeyedSet<K extends Value<T>, T = GenericEmbedded>(x: any): x is KeyedSet<K, T> {
|
||||
static isKeyedSet<T extends Embeddable = GenericEmbedded, K extends CompoundKey<T> = Value<T>>(
|
||||
x: any,
|
||||
): x is KeyedSet<T, K> {
|
||||
return x?.[DictionaryType] === 'Set';
|
||||
}
|
||||
|
||||
constructor(items?: Iterable<K>) {
|
||||
super(canonicalString, items);
|
||||
super(k => k, items);
|
||||
}
|
||||
|
||||
map<S extends Value<R>, R = GenericEmbedded>(f: (value: K) => S): KeyedSet<S, R> {
|
||||
map<R extends Embeddable = GenericEmbedded, S extends Value<R> = Value<R>>(
|
||||
f: (value: K) => S,
|
||||
): KeyedSet<R, S> {
|
||||
return new KeyedSet(_iterMap(this[Symbol.iterator](), f));
|
||||
}
|
||||
|
||||
filter(f: (value: K) => boolean): KeyedSet<K, T> {
|
||||
const result = new KeyedSet<K, T>();
|
||||
filter(f: (value: K) => boolean): KeyedSet<T, K> {
|
||||
const result = new KeyedSet<T, K>();
|
||||
for (let k of this) if (f(k)) result.add(k);
|
||||
return result;
|
||||
}
|
||||
|
||||
clone(): KeyedSet<K, T> {
|
||||
clone(): KeyedSet<T, K> {
|
||||
return new KeyedSet(this);
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() { return 'Set'; }
|
||||
|
||||
__preserve_on__(encoder: Encoder<T>) {
|
||||
if (encoder.canonical) {
|
||||
const pieces = Array.from(this).map<[Bytes, K]>(k => [canonicalEncode(k), k]);
|
||||
pieces.sort((a, b) => Bytes.compare(a[0], b[0]));
|
||||
encoder.encodevalues(Tag.Set, pieces.map(e => e[1]));
|
||||
} else {
|
||||
encoder.encodevalues(Tag.Set, this);
|
||||
}
|
||||
}
|
||||
|
||||
__preserve_text_on__(w: Writer<T>) {
|
||||
w.state.writeSeq('#{', '}', this, vv => w.push(vv));
|
||||
}
|
||||
}
|
||||
|
||||
export class Set<T = GenericEmbedded> extends KeyedSet<Value<T>, T> {
|
||||
static isSet<T = GenericEmbedded>(x: any): x is Set<T> {
|
||||
export class Set<T extends Embeddable = GenericEmbedded> extends KeyedSet<T> {
|
||||
static isSet<T extends Embeddable = GenericEmbedded>(x: any): x is Set<T> {
|
||||
return x?.[DictionaryType] === 'Set';
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | Set<T> {
|
||||
static __from_preserve__<T extends Embeddable>(v: Value<T>): undefined | Set<T> {
|
||||
return Set.isSet<T>(v) ? v : void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeSetOn<T extends Embeddable, V>(
|
||||
s: IdentitySet<V>,
|
||||
encoder: Encoder<T>,
|
||||
encodeV: (v: V, encoder: Encoder<T>) => void,
|
||||
) {
|
||||
if (encoder.canonical) {
|
||||
const canonicalEncoder = new Encoder<T>({
|
||||
canonical: true,
|
||||
embeddedEncode: encoder.embeddedEncode,
|
||||
});
|
||||
const pieces = Array.from(s).map<[Bytes, V]>(v => {
|
||||
encodeV(v, canonicalEncoder);
|
||||
return [canonicalEncoder.contents(), v];
|
||||
});
|
||||
pieces.sort((a, b) => Bytes.compare(a[0], b[0]));
|
||||
encoder.grouped(Tag.Set, () => pieces.forEach(([_e, v]) => encodeV(v, encoder)));
|
||||
} else {
|
||||
encoder.grouped(Tag.Set, () => s.forEach(v => encodeV(v, encoder)));
|
||||
}
|
||||
}
|
||||
|
||||
export function writeSetOn<T extends Embeddable, V>(
|
||||
s: IdentitySet<V>,
|
||||
w: Writer<T>,
|
||||
writeV: (v: V, w: Writer<T>) => void,
|
||||
) {
|
||||
w.state.writeSeq('#{', '}', s, vv => writeV(vv, w));
|
||||
}
|
||||
|
|
|
@ -3,7 +3,17 @@ import type { EncoderState } from "./encoder";
|
|||
import type { Value } from "./values";
|
||||
import { ReaderStateOptions } from "./reader";
|
||||
|
||||
export type EmbeddedTypeEncode<T> = {
|
||||
export const IsEmbedded = Symbol.for('IsEmbedded');
|
||||
|
||||
export interface Embeddable {
|
||||
readonly [IsEmbedded]: true;
|
||||
}
|
||||
|
||||
export function isEmbedded<T extends Embeddable>(v: any): v is T {
|
||||
return !!v?.[IsEmbedded];
|
||||
}
|
||||
|
||||
export type EmbeddedTypeEncode<T extends Embeddable> = {
|
||||
encode(s: EncoderState, v: T): void;
|
||||
}
|
||||
|
||||
|
@ -12,46 +22,22 @@ export type EmbeddedTypeDecode<T> = {
|
|||
fromValue(v: Value<GenericEmbedded>, options: ReaderStateOptions): T;
|
||||
}
|
||||
|
||||
export type EmbeddedType<T> = EmbeddedTypeEncode<T> & EmbeddedTypeDecode<T>;
|
||||
export type EmbeddedType<T extends Embeddable> = EmbeddedTypeEncode<T> & EmbeddedTypeDecode<T>;
|
||||
|
||||
export class Embedded<T> {
|
||||
embeddedValue: T;
|
||||
get [IsEmbedded](): true { return true; }
|
||||
|
||||
constructor(embeddedValue: T) {
|
||||
this.embeddedValue = embeddedValue;
|
||||
constructor(public readonly value: T) {}
|
||||
|
||||
equals(other: any): boolean {
|
||||
return typeof other === 'object' && 'value' in other && Object.is(this.value, other.value);
|
||||
}
|
||||
|
||||
equals(other: any, is: (a: any, b: any) => boolean) {
|
||||
return isEmbedded<T>(other) && is(this.embeddedValue, other.embeddedValue);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return '#!' + (this.embeddedValue as any).toString();
|
||||
}
|
||||
|
||||
__as_preserve__<R>(): T extends R ? Value<R> : never {
|
||||
return this as any;
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | Embedded<T> {
|
||||
return isEmbedded<T>(v) ? v : void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function embed<T>(embeddedValue: T): Embedded<T> {
|
||||
return new Embedded(embeddedValue);
|
||||
}
|
||||
|
||||
export function isEmbedded<T>(v: Value<T>): v is Embedded<T> {
|
||||
return typeof v === 'object' && 'embeddedValue' in v;
|
||||
}
|
||||
|
||||
export class GenericEmbedded {
|
||||
generic: Value;
|
||||
get [IsEmbedded](): true { return true; }
|
||||
|
||||
constructor(generic: Value) {
|
||||
this.generic = generic;
|
||||
}
|
||||
constructor(public readonly generic: Value) {}
|
||||
|
||||
equals(other: any, is: (a: any, b: any) => boolean) {
|
||||
return typeof other === 'object' && 'generic' in other && is(this.generic, other.generic);
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import { Tag } from "./constants";
|
||||
import { Bytes } from "./bytes";
|
||||
import { Bytes, unhexDigit } from "./bytes";
|
||||
import { Value } from "./values";
|
||||
import { EncodeError } from "./codec";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { GenericEmbedded, EmbeddedTypeEncode } from "./embedded";
|
||||
import type { Embedded } from "./embedded";
|
||||
import { EmbeddedTypeEncode, isEmbedded } from "./embedded";
|
||||
import type { Embeddable } from "./embedded";
|
||||
import { DictionaryMap, encodeDictionaryOn } from "./dictionary";
|
||||
|
||||
export type Encodable<T> =
|
||||
export type Encodable<T extends Embeddable> =
|
||||
Value<T> | Preservable<T> | Iterable<Value<T>> | ArrayBufferView;
|
||||
|
||||
export interface Preservable<T> {
|
||||
export interface Preservable<T extends Embeddable> {
|
||||
__preserve_on__(encoder: Encoder<T>): void;
|
||||
}
|
||||
|
||||
export function isPreservable<T>(v: any): v is Preservable<T> {
|
||||
export function isPreservable<T extends Embeddable>(v: any): v is Preservable<T> {
|
||||
return typeof v === 'object' && v !== null && '__preserve_on__' in v && typeof v.__preserve_on__ === 'function';
|
||||
}
|
||||
|
||||
|
@ -22,11 +23,11 @@ export interface EncoderOptions {
|
|||
includeAnnotations?: boolean;
|
||||
}
|
||||
|
||||
export interface EncoderEmbeddedOptions<T> extends EncoderOptions {
|
||||
export interface EncoderEmbeddedOptions<T extends Embeddable> extends EncoderOptions {
|
||||
embeddedEncode?: EmbeddedTypeEncode<T>;
|
||||
}
|
||||
|
||||
export function asLatin1(bs: Uint8Array): string {
|
||||
function asLatin1(bs: Uint8Array): string {
|
||||
return String.fromCharCode.apply(null, bs as any as number[]);
|
||||
}
|
||||
|
||||
|
@ -122,6 +123,13 @@ export class EncoderState {
|
|||
this.index += bs.length;
|
||||
}
|
||||
|
||||
claimbytes(count: number) {
|
||||
this.makeroom(count);
|
||||
const view = new Uint8Array(this.view.buffer, this.index, count);
|
||||
this.index += count;
|
||||
return view;
|
||||
}
|
||||
|
||||
varint(v: number) {
|
||||
while (v >= 128) {
|
||||
this.emitbyte((v % 128) + 128);
|
||||
|
@ -130,17 +138,21 @@ export class EncoderState {
|
|||
this.emitbyte(v);
|
||||
}
|
||||
|
||||
encodeint(v: number) {
|
||||
// TODO: Bignums :-/
|
||||
const plain_bitcount = Math.floor(Math.log2(v > 0 ? v : -(1 + v))) + 1;
|
||||
encodeint(v: number | bigint) {
|
||||
if (typeof v === 'bigint') return this.encodebigint(v);
|
||||
|
||||
this.emitbyte(Tag.SignedInteger);
|
||||
|
||||
if (v === 0) {
|
||||
this.emitbyte(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const plain_bitcount = v === -1 ? 0 : Math.floor(Math.log2(v > 0 ? v : -(1 + v))) + 1;
|
||||
const signed_bitcount = plain_bitcount + 1;
|
||||
const bytecount = (signed_bitcount + 7) >> 3;
|
||||
if (bytecount <= 16) {
|
||||
this.emitbyte(Tag.MediumInteger_lo + bytecount - 1);
|
||||
} else {
|
||||
this.emitbyte(Tag.SignedInteger);
|
||||
this.varint(bytecount);
|
||||
}
|
||||
this.varint(bytecount);
|
||||
|
||||
const enc = (n: number, x: number) => {
|
||||
if (n > 0) {
|
||||
enc(n - 1, Math.floor(x / 256));
|
||||
|
@ -150,6 +162,37 @@ export class EncoderState {
|
|||
enc(bytecount, v);
|
||||
}
|
||||
|
||||
encodebigint(v: bigint) {
|
||||
this.emitbyte(Tag.SignedInteger);
|
||||
|
||||
let hex: string;
|
||||
if (v > 0) {
|
||||
hex = v.toString(16);
|
||||
if (hex.length & 1) {
|
||||
hex = '0' + hex;
|
||||
} else if (unhexDigit(hex.charCodeAt(0)) >= 8) {
|
||||
hex = '00' + hex;
|
||||
}
|
||||
} else if (v < 0) {
|
||||
const negatedHex = (~v).toString(16);
|
||||
hex = '';
|
||||
for (let i = 0; i < negatedHex.length; i++) {
|
||||
hex = hex + 'fedcba9876543210'[unhexDigit(negatedHex.charCodeAt(i))];
|
||||
}
|
||||
if (hex.length & 1) {
|
||||
hex = 'f' + hex;
|
||||
} else if (unhexDigit(hex.charCodeAt(0)) < 8) {
|
||||
hex = 'ff' + hex;
|
||||
}
|
||||
} else {
|
||||
this.emitbyte(0);
|
||||
return;
|
||||
}
|
||||
|
||||
this.varint(hex.length >> 1);
|
||||
Bytes._raw_fromHexInto(hex, this.claimbytes(hex.length >> 1));
|
||||
}
|
||||
|
||||
encodebytes(tag: Tag, bs: Uint8Array) {
|
||||
this.emitbyte(tag);
|
||||
this.varint(bs.length);
|
||||
|
@ -157,7 +200,7 @@ export class EncoderState {
|
|||
}
|
||||
}
|
||||
|
||||
export class Encoder<T = object> {
|
||||
export class Encoder<T extends Embeddable> {
|
||||
state: EncoderState;
|
||||
embeddedEncode: EmbeddedTypeEncode<T>;
|
||||
|
||||
|
@ -176,7 +219,7 @@ export class Encoder<T = object> {
|
|||
}
|
||||
}
|
||||
|
||||
withEmbeddedEncode<S>(
|
||||
withEmbeddedEncode<S extends Embeddable>(
|
||||
embeddedEncode: EmbeddedTypeEncode<S>,
|
||||
body: (e: Encoder<S>) => void): this
|
||||
{
|
||||
|
@ -200,14 +243,14 @@ export class Encoder<T = object> {
|
|||
return this.state.contentsString();
|
||||
}
|
||||
|
||||
encodevalues(tag: Tag, items: Iterable<Value<T>>) {
|
||||
grouped(tag: Tag, f: () => void) {
|
||||
this.state.emitbyte(tag);
|
||||
for (let i of items) { this.push(i); }
|
||||
f();
|
||||
this.state.emitbyte(Tag.End);
|
||||
}
|
||||
|
||||
push(v: Encodable<T>) {
|
||||
if (isPreservable<unknown>(v)) {
|
||||
if (isPreservable<any>(v)) {
|
||||
v.__preserve_on__(this);
|
||||
}
|
||||
else if (isPreservable<T>(v)) {
|
||||
|
@ -216,12 +259,8 @@ export class Encoder<T = object> {
|
|||
else if (typeof v === 'boolean') {
|
||||
this.state.emitbyte(v ? Tag.True : Tag.False);
|
||||
}
|
||||
else if (typeof v === 'number') {
|
||||
if (v >= -3 && v <= 12) {
|
||||
this.state.emitbyte(Tag.SmallInteger_lo + ((v + 16) & 0xf));
|
||||
} else {
|
||||
this.state.encodeint(v);
|
||||
}
|
||||
else if (typeof v === 'number' || typeof v === 'bigint') {
|
||||
this.state.encodeint(v);
|
||||
}
|
||||
else if (typeof v === 'string') {
|
||||
this.state.encodebytes(Tag.String, new Bytes(v)._view);
|
||||
|
@ -246,19 +285,25 @@ export class Encoder<T = object> {
|
|||
this.state.emitbyte(Tag.End);
|
||||
}
|
||||
else if (isIterable<Value<T>>(v)) {
|
||||
this.encodevalues(Tag.Sequence, v);
|
||||
this.grouped(Tag.Sequence, () => {
|
||||
for (let i of v) this.push(i);
|
||||
});
|
||||
}
|
||||
else if (isEmbedded<T>(v)) {
|
||||
this.state.emitbyte(Tag.Embedded);
|
||||
this.embeddedEncode.encode(this.state, v);
|
||||
}
|
||||
else {
|
||||
((v: Embedded<T>) => {
|
||||
this.state.emitbyte(Tag.Embedded);
|
||||
this.embeddedEncode.encode(this.state, v.embeddedValue);
|
||||
})(v);
|
||||
encodeDictionaryOn(new DictionaryMap<T>(v),
|
||||
this,
|
||||
(k, e) => e.push(k),
|
||||
(v, e) => e.push(v));
|
||||
}
|
||||
return this; // for chaining
|
||||
}
|
||||
}
|
||||
|
||||
export function encode<T>(
|
||||
export function encode<T extends Embeddable>(
|
||||
v: Encodable<T>,
|
||||
options: EncoderEmbeddedOptions<T> = {}): Bytes
|
||||
{
|
||||
|
@ -292,7 +337,9 @@ export function canonicalString(v: Encodable<any>): string {
|
|||
}
|
||||
}
|
||||
|
||||
export function encodeWithAnnotations<T>(v: Encodable<T>,
|
||||
options: EncoderEmbeddedOptions<T> = {}): Bytes {
|
||||
export function encodeWithAnnotations<T extends Embeddable>(
|
||||
v: Encodable<T>,
|
||||
options: EncoderEmbeddedOptions<T> = {},
|
||||
): Bytes {
|
||||
return encode(v, { ... options, includeAnnotations: true });
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Tag } from "./constants";
|
||||
import { stringify } from "./text";
|
||||
import { Value } from "./values";
|
||||
import type { GenericEmbedded } from "./embedded";
|
||||
import type { Embeddable, GenericEmbedded } from "./embedded";
|
||||
import type { Encoder, Preservable } from "./encoder";
|
||||
import type { Writer, PreserveWritable } from "./writer";
|
||||
import { Bytes, dataview, underlying } from "./bytes";
|
||||
import { Bytes, dataview } from "./bytes";
|
||||
|
||||
export type FloatType = 'Single' | 'Double';
|
||||
// v Previously included 'Single'; may again in future. Also, 'Half', 'Quad'?
|
||||
export type FloatType = 'Double';
|
||||
export const FloatType = Symbol.for('FloatType');
|
||||
|
||||
export abstract class Float {
|
||||
|
@ -16,8 +16,8 @@ export abstract class Float {
|
|||
this.value = typeof value === 'number' ? value : value.value;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return stringify(this);
|
||||
__preserve_text_on__(w: Writer<any>) {
|
||||
w.state.pieces.push(this.toString());
|
||||
}
|
||||
|
||||
abstract toBytes(): Bytes;
|
||||
|
@ -38,7 +38,6 @@ export abstract class Float {
|
|||
abstract get [FloatType](): FloatType;
|
||||
|
||||
static isFloat = (x: any): x is Float => x?.[FloatType] !== void 0;
|
||||
static isSingle = (x: any): x is SingleFloat => x?.[FloatType] === 'Single';
|
||||
static isDouble = (x: any): x is DoubleFloat => x?.[FloatType] === 'Double';
|
||||
}
|
||||
|
||||
|
@ -59,78 +58,42 @@ export function floatlikeString(f: number): string {
|
|||
return s + '.0';
|
||||
}
|
||||
|
||||
export class SingleFloat extends Float implements Preservable<any>, PreserveWritable<any> {
|
||||
__as_preserve__<T = GenericEmbedded>(): Value<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
static fromBytes(bs: Bytes | DataView): SingleFloat {
|
||||
const view = dataview(bs);
|
||||
const vf = view.getInt32(0, false);
|
||||
if ((vf & 0x7f800000) === 0x7f800000) {
|
||||
// NaN or inf. Preserve quiet/signalling bit by manually expanding to double-precision.
|
||||
const sign = vf >> 31;
|
||||
const payload = vf & 0x007fffff;
|
||||
const dbs = new Bytes(8);
|
||||
const dview = dataview(dbs);
|
||||
dview.setInt16(0, (sign << 15) | 0x7ff0 | (payload >> 19), false);
|
||||
dview.setInt32(2, (payload & 0x7ffff) << 13, false);
|
||||
return new SingleFloat(dview.getFloat64(0, false));
|
||||
} else {
|
||||
return new SingleFloat(dataview(bs).getFloat32(0, false));
|
||||
}
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | SingleFloat {
|
||||
return Float.isSingle(v) ? v : void 0;
|
||||
}
|
||||
|
||||
__w(v: DataView, offset: number) {
|
||||
if (Number.isNaN(this.value)) {
|
||||
const dbs = new Bytes(8);
|
||||
const dview = dataview(dbs);
|
||||
dview.setFloat64(0, this.value, false);
|
||||
const sign = dview.getInt8(0) >> 7;
|
||||
const payload = (dview.getInt32(1, false) >> 5) & 0x007fffff;
|
||||
const vf = (sign << 31) | 0x7f800000 | payload;
|
||||
v.setInt32(offset, vf, false);
|
||||
} else {
|
||||
v.setFloat32(offset, this.value, false);
|
||||
}
|
||||
}
|
||||
|
||||
__preserve_on__(encoder: Encoder<any>) {
|
||||
encoder.state.emitbyte(Tag.Float);
|
||||
encoder.state.makeroom(4);
|
||||
this.__w(encoder.state.view, encoder.state.index);
|
||||
encoder.state.index += 4;
|
||||
}
|
||||
|
||||
toBytes(): Bytes {
|
||||
const bs = new Bytes(4);
|
||||
this.__w(bs.dataview(), 0);
|
||||
return bs;
|
||||
}
|
||||
|
||||
__preserve_text_on__(w: Writer<any>) {
|
||||
if (Number.isFinite(this.value)) {
|
||||
w.state.pieces.push(floatlikeString(this.value) + 'f');
|
||||
} else {
|
||||
w.state.pieces.push('#xf"', this.toBytes().toHex(), '"');
|
||||
}
|
||||
}
|
||||
|
||||
get [FloatType](): 'Single' {
|
||||
return 'Single';
|
||||
}
|
||||
}
|
||||
|
||||
export function Single(value: number | Float): SingleFloat {
|
||||
return new SingleFloat(value);
|
||||
}
|
||||
// -- These snippets are useful to keep in mind for promoting 4-byte, single-precision floats
|
||||
// -- to 8-byte, double-precision floats *while preserving NaN bit-patterns*:
|
||||
//
|
||||
// static fromBytes(bs: Bytes | DataView): SingleFloat {
|
||||
// const view = dataview(bs);
|
||||
// const vf = view.getInt32(0, false);
|
||||
// if ((vf & 0x7f800000) === 0x7f800000) {
|
||||
// // NaN or inf. Preserve quiet/signalling bit by manually expanding to double-precision.
|
||||
// const sign = vf >> 31;
|
||||
// const payload = vf & 0x007fffff;
|
||||
// const dbs = new Bytes(8);
|
||||
// const dview = dataview(dbs);
|
||||
// dview.setInt16(0, (sign << 15) | 0x7ff0 | (payload >> 19), false);
|
||||
// dview.setInt32(2, (payload & 0x7ffff) << 13, false);
|
||||
// return new SingleFloat(dview.getFloat64(0, false));
|
||||
// } else {
|
||||
// return new SingleFloat(dataview(bs).getFloat32(0, false));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// __w(v: DataView, offset: number) {
|
||||
// if (Number.isNaN(this.value)) {
|
||||
// const dbs = new Bytes(8);
|
||||
// const dview = dataview(dbs);
|
||||
// dview.setFloat64(0, this.value, false);
|
||||
// const sign = dview.getInt8(0) >> 7;
|
||||
// const payload = (dview.getInt32(1, false) >> 5) & 0x007fffff;
|
||||
// const vf = (sign << 31) | 0x7f800000 | payload;
|
||||
// v.setInt32(offset, vf, false);
|
||||
// } else {
|
||||
// v.setFloat32(offset, this.value, false);
|
||||
// }
|
||||
// }
|
||||
|
||||
export class DoubleFloat extends Float implements Preservable<any>, PreserveWritable<any> {
|
||||
__as_preserve__<T = GenericEmbedded>(): Value<T> {
|
||||
__as_preserve__<T extends Embeddable = GenericEmbedded>(): Value<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -138,12 +101,13 @@ export class DoubleFloat extends Float implements Preservable<any>, PreserveWrit
|
|||
return new DoubleFloat(dataview(bs).getFloat64(0, false));
|
||||
}
|
||||
|
||||
static __from_preserve__<T>(v: Value<T>): undefined | DoubleFloat {
|
||||
static __from_preserve__<T extends Embeddable>(v: Value<T>): undefined | DoubleFloat {
|
||||
return Float.isDouble(v) ? v : void 0;
|
||||
}
|
||||
|
||||
__preserve_on__(encoder: Encoder<any>) {
|
||||
encoder.state.emitbyte(Tag.Double);
|
||||
encoder.state.emitbyte(Tag.Ieee754);
|
||||
encoder.state.emitbyte(8);
|
||||
encoder.state.makeroom(8);
|
||||
encoder.state.view.setFloat64(encoder.state.index, this.value, false);
|
||||
encoder.state.index += 8;
|
||||
|
@ -155,11 +119,11 @@ export class DoubleFloat extends Float implements Preservable<any>, PreserveWrit
|
|||
return bs;
|
||||
}
|
||||
|
||||
__preserve_text_on__(w: Writer<any>) {
|
||||
toString(): string {
|
||||
if (Number.isFinite(this.value)) {
|
||||
w.state.pieces.push(floatlikeString(this.value));
|
||||
return floatlikeString(this.value);
|
||||
} else {
|
||||
w.state.pieces.push('#xd"', this.toBytes().toHex(), '"');
|
||||
return '#xd"' + this.toBytes().toHex() + '"';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import { Record, Tuple } from "./record";
|
||||
import { Bytes } from "./bytes";
|
||||
import { Value } from "./values";
|
||||
import { Set, Dictionary } from "./dictionary";
|
||||
import { Set, KeyedDictionary, Dictionary, DictionaryMap } from "./dictionary";
|
||||
import { annotate, Annotated } from "./annotated";
|
||||
import { Double, Float, Single } from "./float";
|
||||
import { Embedded } from "./embedded";
|
||||
import { Double, Float } from "./float";
|
||||
import { Embeddable, isEmbedded } from "./embedded";
|
||||
|
||||
export enum ValueClass {
|
||||
Boolean,
|
||||
Float,
|
||||
Double,
|
||||
SignedInteger,
|
||||
String,
|
||||
|
@ -22,13 +21,12 @@ export enum ValueClass {
|
|||
Annotated, // quasi-class
|
||||
}
|
||||
|
||||
export type Fold<T, R = Value<T>> = (v: Value<T>) => R;
|
||||
export type Fold<T extends Embeddable, R = Value<T>> = (v: Value<T>) => R;
|
||||
|
||||
export interface FoldMethods<T, R> {
|
||||
export interface FoldMethods<T extends Embeddable, R> {
|
||||
boolean(b: boolean): R;
|
||||
single(f: number): R;
|
||||
double(f: number): R;
|
||||
integer(i: number): R;
|
||||
integer(i: number | bigint): R;
|
||||
string(s: string): R;
|
||||
bytes(b: Bytes): R;
|
||||
symbol(s: symbol): R;
|
||||
|
@ -36,50 +34,46 @@ export interface FoldMethods<T, R> {
|
|||
record(r: Record<Value<T>, Tuple<Value<T>>, T>, k: Fold<T, R>): R;
|
||||
array(a: Array<Value<T>>, k: Fold<T, R>): R;
|
||||
set(s: Set<T>, k: Fold<T, R>): R;
|
||||
dictionary(d: Dictionary<T>, k: Fold<T, R>): R;
|
||||
dictionary(d: DictionaryMap<T>, k: Fold<T, R>): R;
|
||||
|
||||
annotated(a: Annotated<T>, k: Fold<T, R>): R;
|
||||
|
||||
embedded(t: Embedded<T>, k: Fold<T, R>): R;
|
||||
embedded(t: T, k: Fold<T, R>): R;
|
||||
}
|
||||
|
||||
export class VoidFold<T> implements FoldMethods<T, void> {
|
||||
boolean(b: boolean): void {}
|
||||
single(f: number): void {}
|
||||
double(f: number): void {}
|
||||
integer(i: number): void {}
|
||||
string(s: string): void {}
|
||||
bytes(b: Bytes): void {}
|
||||
symbol(s: symbol): void {}
|
||||
export class VoidFold<T extends Embeddable> implements FoldMethods<T, void> {
|
||||
boolean(_b: boolean): void {}
|
||||
double(_f: number): void {}
|
||||
integer(_i: number | bigint): void {}
|
||||
string(_s: string): void {}
|
||||
bytes(_b: Bytes): void {}
|
||||
symbol(_s: symbol): void {}
|
||||
record(r: Record<Value<T>, Tuple<Value<T>>, T>, k: Fold<T, void>): void {
|
||||
k(r.label);
|
||||
r.forEach(k);
|
||||
}
|
||||
array(a: Value<T>[], k: Fold<T, void>): void { a.forEach(k); }
|
||||
set(s: Set<T>, k: Fold<T, void>): void { s.forEach(k); }
|
||||
dictionary(d: Dictionary<T>, k: Fold<T, void>): void {
|
||||
dictionary(d: DictionaryMap<T>, k: Fold<T, void>): void {
|
||||
d.forEach((value, key) => { k(key); k(value); });
|
||||
}
|
||||
annotated(a: Annotated<T>, k: Fold<T, void>): void { k(a.item); a.annotations.forEach(k); }
|
||||
embedded(_t: Embedded<T>, _k: Fold<T, void>): void {}
|
||||
embedded(_t: T, _k: Fold<T, void>): void {}
|
||||
}
|
||||
|
||||
export class ForEachEmbedded<T> extends VoidFold<T> {
|
||||
export class ForEachEmbedded<T extends Embeddable> extends VoidFold<T> {
|
||||
constructor(public readonly f: (t: T, k: Fold<T, void>) => void) { super(); }
|
||||
embedded(t: Embedded<T>, k: Fold<T, void>): void { this.f(t.embeddedValue, k); }
|
||||
embedded(t: T, k: Fold<T, void>): void { this.f(t, k); }
|
||||
}
|
||||
|
||||
export abstract class ValueFold<T, R = T> implements FoldMethods<T, Value<R>> {
|
||||
export abstract class ValueFold<T extends Embeddable, R extends Embeddable = T> implements FoldMethods<T, Value<R>> {
|
||||
boolean(b: boolean): Value<R> {
|
||||
return b;
|
||||
}
|
||||
single(f: number): Value<R> {
|
||||
return Single(f);
|
||||
}
|
||||
double(f: number): Value<R> {
|
||||
return Double(f);
|
||||
}
|
||||
integer(i: number): Value<R> {
|
||||
integer(i: number | bigint): Value<R> {
|
||||
return i;
|
||||
}
|
||||
string(s: string): Value<R> {
|
||||
|
@ -100,22 +94,24 @@ export abstract class ValueFold<T, R = T> implements FoldMethods<T, Value<R>> {
|
|||
set(s: Set<T>, k: Fold<T, Value<R>>): Value<R> {
|
||||
return s.map(k);
|
||||
}
|
||||
dictionary(d: Dictionary<T>, k: Fold<T, Value<R>>): Value<R> {
|
||||
return d.mapEntries(([key, value]) => [k(key), k(value)]);
|
||||
dictionary(d: DictionaryMap<T>, k: Fold<T, Value<R>>): Value<R> {
|
||||
const result = new DictionaryMap<R>();
|
||||
d.forEach((value, key) => result.set(k(key), k(value)));
|
||||
return result.simplifiedValue();
|
||||
}
|
||||
annotated(a: Annotated<T>, k: Fold<T, Value<R>>): Value<R> {
|
||||
return annotate(k(a.item), ...a.annotations.map(k));
|
||||
}
|
||||
abstract embedded(t: Embedded<T>, k: Fold<T, Value<R>>): Value<R>;
|
||||
abstract embedded(t: T, k: Fold<T, Value<R>>): Value<R>;
|
||||
}
|
||||
|
||||
export class IdentityFold<T> extends ValueFold<T, T> {
|
||||
embedded(t: Embedded<T>, _k: Fold<T, Value<T>>): Value<T> {
|
||||
export class IdentityFold<T extends Embeddable> extends ValueFold<T, T> {
|
||||
embedded(t: T, _k: Fold<T, Value<T>>): Value<T> {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
export class MapFold<T, R> extends ValueFold<T, R> {
|
||||
export class MapFold<T extends Embeddable, R extends Embeddable> extends ValueFold<T, R> {
|
||||
readonly f: (t: T) => Value<R>;
|
||||
|
||||
constructor(f: (t: T) => Value<R>) {
|
||||
|
@ -123,21 +119,23 @@ export class MapFold<T, R> extends ValueFold<T, R> {
|
|||
this.f = f;
|
||||
}
|
||||
|
||||
embedded(t: Embedded<T>, _k: Fold<T, Value<R>>): Value<R> {
|
||||
return this.f(t.embeddedValue);
|
||||
embedded(t: T, _k: Fold<T, Value<R>>): Value<R> {
|
||||
return this.f(t);
|
||||
}
|
||||
}
|
||||
|
||||
export function valueClass<T>(v: Value<T>): ValueClass {
|
||||
export function valueClass<T extends Embeddable>(v: Value<T>): ValueClass {
|
||||
switch (typeof v) {
|
||||
case 'boolean':
|
||||
return ValueClass.Boolean;
|
||||
case 'number':
|
||||
if (!Number.isInteger(v)) {
|
||||
throw new Error("Non-integer number in Preserves valueClass; missing SingleFloat/DoubleFloat wrapper?");
|
||||
throw new Error("Non-integer number in Preserves valueClass; missing Float wrapper?");
|
||||
} else {
|
||||
return ValueClass.SignedInteger;
|
||||
}
|
||||
case 'bigint':
|
||||
return ValueClass.SignedInteger;
|
||||
case 'string':
|
||||
return ValueClass.String;
|
||||
case 'symbol':
|
||||
|
@ -155,12 +153,10 @@ export function valueClass<T>(v: Value<T>): ValueClass {
|
|||
return ValueClass.Annotated;
|
||||
} else if (Bytes.isBytes(v)) {
|
||||
return ValueClass.ByteString;
|
||||
} else if (Float.isSingle(v)) {
|
||||
return ValueClass.Float;
|
||||
} else if (Float.isDouble(v)) {
|
||||
return ValueClass.Double;
|
||||
} else {
|
||||
return ValueClass.Embedded;
|
||||
return ((_v: T) => ValueClass.Embedded)(v);
|
||||
}
|
||||
default:
|
||||
((_v: never): never => { throw new Error("Internal error"); })(v);
|
||||
|
@ -169,7 +165,7 @@ export function valueClass<T>(v: Value<T>): ValueClass {
|
|||
|
||||
export const IDENTITY_FOLD = new IdentityFold<any>();
|
||||
|
||||
export function fold<T, R>(v: Value<T>, o: FoldMethods<T, R>): R {
|
||||
export function fold<T extends Embeddable, R>(v: Value<T>, o: FoldMethods<T, R>): R {
|
||||
const walk = (v: Value<T>): R => {
|
||||
switch (typeof v) {
|
||||
case 'boolean':
|
||||
|
@ -181,6 +177,8 @@ export function fold<T, R>(v: Value<T>, o: FoldMethods<T, R>): R {
|
|||
} else {
|
||||
return o.integer(v);
|
||||
}
|
||||
case 'bigint':
|
||||
return o.integer(v);
|
||||
case 'string':
|
||||
return o.string(v);
|
||||
case 'symbol':
|
||||
|
@ -192,18 +190,16 @@ export function fold<T, R>(v: Value<T>, o: FoldMethods<T, R>): R {
|
|||
return o.array(v, walk);
|
||||
} else if (Set.isSet<T>(v)) {
|
||||
return o.set(v, walk);
|
||||
} else if (Dictionary.isDictionary<T>(v)) {
|
||||
return o.dictionary(v, walk);
|
||||
} else if (isEmbedded(v)) {
|
||||
return o.embedded(v, walk);
|
||||
} else if (Annotated.isAnnotated<T>(v)) {
|
||||
return o.annotated(v, walk);
|
||||
} else if (Bytes.isBytes(v)) {
|
||||
return o.bytes(v);
|
||||
} else if (Float.isSingle(v)) {
|
||||
return o.single(v.value);
|
||||
} else if (Float.isDouble(v)) {
|
||||
return o.double(v.value);
|
||||
} else {
|
||||
return o.embedded(v, walk);
|
||||
} else if (Dictionary.isDictionary<T>(v)) {
|
||||
return o.dictionary(new DictionaryMap(v), walk);
|
||||
}
|
||||
default:
|
||||
((_v: never): never => { throw new Error("Internal error"); })(v);
|
||||
|
@ -212,7 +208,7 @@ export function fold<T, R>(v: Value<T>, o: FoldMethods<T, R>): R {
|
|||
return walk(v);
|
||||
}
|
||||
|
||||
export function mapEmbeddeds<T, R>(
|
||||
export function mapEmbeddeds<T extends Embeddable, R extends Embeddable>(
|
||||
v: Value<T>,
|
||||
f: (t: T) => Value<R>,
|
||||
): Value<R>
|
||||
|
@ -220,6 +216,9 @@ export function mapEmbeddeds<T, R>(
|
|||
return fold(v, new MapFold(f));
|
||||
}
|
||||
|
||||
export function forEachEmbedded<T>(v: Value<T>, f: (t: T, k: Fold<T, void>) => void): void {
|
||||
export function forEachEmbedded<T extends Embeddable>(
|
||||
v: Value<T>,
|
||||
f: (t: T, k: Fold<T, void>) => void,
|
||||
): void {
|
||||
return fold(v, new ForEachEmbedded(f));
|
||||
}
|
||||
|
|
|
@ -1,72 +1,97 @@
|
|||
import { embed, GenericEmbedded } from "./embedded";
|
||||
import { Embeddable, GenericEmbedded, isEmbedded } from "./embedded";
|
||||
import { Bytes } from "./bytes";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { Value } from "./values";
|
||||
import { Dictionary, Set } from "./dictionary";
|
||||
import { Dictionary, KeyedDictionary, Set } from "./dictionary";
|
||||
import { JsDictionary } from "./jsdictionary";
|
||||
|
||||
export function fromJS<T = GenericEmbedded>(x: any): Value<T> {
|
||||
switch (typeof x) {
|
||||
case 'number':
|
||||
if (!Number.isInteger(x)) {
|
||||
// We require that clients be explicit about integer vs. non-integer types.
|
||||
throw new TypeError("Refusing to autoconvert non-integer number to Single or Double");
|
||||
}
|
||||
export interface FromJSOptions<T extends Embeddable = GenericEmbedded> {
|
||||
onNonInteger?(n: number): Value<T> | undefined;
|
||||
}
|
||||
|
||||
export function fromJS<T extends Embeddable = GenericEmbedded>(x: any): Value<T> {
|
||||
return fromJS_options(x);
|
||||
}
|
||||
|
||||
export function fromJS_options<T extends Embeddable = GenericEmbedded>(x: any, options?: FromJSOptions<T>): Value<T> {
|
||||
function walk(x: any): Value<T> {
|
||||
switch (typeof x) {
|
||||
case 'number':
|
||||
if (!Number.isInteger(x)) {
|
||||
// We require that clients be explicit about integer vs. non-integer types.
|
||||
const converted = options?.onNonInteger?.(x) ?? void 0;
|
||||
if (converted !== void 0) return converted;
|
||||
throw new TypeError("Refusing to autoconvert non-integer number to Double");
|
||||
}
|
||||
// FALL THROUGH
|
||||
case 'string':
|
||||
case 'symbol':
|
||||
case 'boolean':
|
||||
return x;
|
||||
|
||||
case 'undefined':
|
||||
case 'function':
|
||||
case 'bigint':
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (x === null) {
|
||||
break;
|
||||
}
|
||||
if (typeof x.__as_preserve__ === 'function') {
|
||||
return x.__as_preserve__();
|
||||
}
|
||||
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(x)) {
|
||||
case 'bigint':
|
||||
case 'string':
|
||||
case 'symbol':
|
||||
case 'boolean':
|
||||
return x;
|
||||
}
|
||||
if (Array.isArray(x)) {
|
||||
return x.map<Value<T>>(fromJS);
|
||||
}
|
||||
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
|
||||
return Bytes.from(x);
|
||||
}
|
||||
if (Map.isMap(x)) {
|
||||
const d = new Dictionary<T>();
|
||||
x.forEach((v, k) => d.set(fromJS(k), fromJS(v)));
|
||||
return d;
|
||||
}
|
||||
if (Set.isSet(x)) {
|
||||
const s = new Set<T>();
|
||||
x.forEach(v => s.add(fromJS(v)));
|
||||
return s;
|
||||
}
|
||||
// Just... assume it's a T.
|
||||
return embed(x as T);
|
||||
|
||||
default:
|
||||
break;
|
||||
case 'undefined':
|
||||
case 'function':
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (x === null) {
|
||||
break;
|
||||
}
|
||||
if (typeof x.__as_preserve__ === 'function') {
|
||||
return x.__as_preserve__();
|
||||
}
|
||||
if (Record.isRecord<Value<T>, Tuple<Value<T>>, T>(x)) {
|
||||
return x;
|
||||
}
|
||||
if (Array.isArray(x)) {
|
||||
return x.map<Value<T>>(walk);
|
||||
}
|
||||
if (ArrayBuffer.isView(x) || x instanceof ArrayBuffer) {
|
||||
return Bytes.from(x);
|
||||
}
|
||||
if (Map.isMap(x)) {
|
||||
const d = new KeyedDictionary<T>();
|
||||
x.forEach((v, k) => d.set(walk(k), walk(v)));
|
||||
return d;
|
||||
}
|
||||
if (Set.isSet(x)) {
|
||||
const s = new Set<T>();
|
||||
x.forEach(v => s.add(walk(v)));
|
||||
return s;
|
||||
}
|
||||
if (isEmbedded<T>(x)) {
|
||||
return x;
|
||||
}
|
||||
// Handle plain JS objects to build a JsDictionary
|
||||
{
|
||||
const r: JsDictionary<Value<T>> = {};
|
||||
Object.entries(x).forEach(([k, v]) => r[k] = walk(v));
|
||||
return r;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
throw new TypeError("Cannot represent JavaScript value as Preserves: " + x);
|
||||
}
|
||||
|
||||
throw new TypeError("Cannot represent JavaScript value as Preserves: " + x);
|
||||
return walk(x);
|
||||
}
|
||||
|
||||
declare module "./dictionary" {
|
||||
namespace Dictionary {
|
||||
export function fromJS<T = GenericEmbedded, V = GenericEmbedded>(x: object): Dictionary<T, Value<V>>;
|
||||
export function stringMap<T extends Embeddable = GenericEmbedded>(
|
||||
x: object
|
||||
): KeyedDictionary<T, string, Value<T>>;
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary.fromJS = function <T = GenericEmbedded, V = GenericEmbedded>(x: object): Dictionary<T, Value<V>> {
|
||||
if (Dictionary.isDictionary<T, Value<V>>(x)) return x;
|
||||
const d = new Dictionary<T, Value<V>>();
|
||||
Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value)));
|
||||
return d;
|
||||
Dictionary.stringMap = function <T extends Embeddable = GenericEmbedded>(
|
||||
x: object
|
||||
): KeyedDictionary<T, string, Value<T>> {
|
||||
const r = new KeyedDictionary<T, string, Value<T>>();
|
||||
Object.entries(x).forEach(([key, value]) => r.set(key, fromJS(value)));
|
||||
return r;
|
||||
};
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
export * from './runtime';
|
||||
export * as Constants from './constants';
|
||||
export * as Pexpr from './pexpr';
|
||||
|
||||
import type { Embeddable } from './embedded';
|
||||
import type { Value } from './values';
|
||||
|
||||
declare global {
|
||||
interface ArrayConstructor {
|
||||
__from_preserve__<T>(v: Value<T>): undefined | Array<Value<T>>;
|
||||
__from_preserve__<T extends Embeddable>(v: Value<T>): undefined | Array<Value<T>>;
|
||||
}
|
||||
}
|
||||
|
||||
Array.__from_preserve__ = <T>(v: Value<T>) => {
|
||||
Array.__from_preserve__ = <T extends Embeddable>(v: Value<T>) => {
|
||||
return Array.isArray(v) ? v : void 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import type { GenericEmbedded } from "./embedded";
|
||||
import type { Embeddable, GenericEmbedded } from "./embedded";
|
||||
import type { Annotated } from "./annotated";
|
||||
import { Dictionary } from "./dictionary";
|
||||
|
||||
export const IsPreservesAnnotated = Symbol.for('IsPreservesAnnotated');
|
||||
|
||||
export function isAnnotated<T = GenericEmbedded>(x: any): x is Annotated<T>
|
||||
export function isAnnotated<T extends Embeddable = GenericEmbedded>(x: any): x is Annotated<T>
|
||||
{
|
||||
return !!x?.[IsPreservesAnnotated];
|
||||
}
|
||||
|
@ -12,7 +13,13 @@ export function is(a: any, b: any): boolean {
|
|||
if (isAnnotated(a)) a = a.item;
|
||||
if (isAnnotated(b)) b = b.item;
|
||||
if (Object.is(a, b)) return true;
|
||||
if (typeof a !== typeof b) return false;
|
||||
if (typeof a !== typeof b) {
|
||||
if ((typeof a === 'number' && typeof b === 'bigint') ||
|
||||
(typeof a === 'bigint' && typeof b === 'number')) {
|
||||
return a == b;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (typeof a === 'object') {
|
||||
if (a === null || b === null) return false;
|
||||
if ('equals' in a && typeof a.equals === 'function') return a.equals(b, is);
|
||||
|
@ -24,6 +31,17 @@ export function is(a: any, b: any): boolean {
|
|||
for (let i = 0; i < a.length; i++) if (!is(a[i], b[i])) return false;
|
||||
return true;
|
||||
}
|
||||
{
|
||||
const aMap = Dictionary.asMap(a);
|
||||
const bMap = Dictionary.asMap(b);
|
||||
if (!aMap || !bMap) return false;
|
||||
if (aMap.size !== bMap.size) return false;
|
||||
for (const k of aMap.keys()) {
|
||||
if (!bMap.has(k)) return false;
|
||||
if (!is(aMap.get(k), bMap.get(k))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
import { isEmbedded } from './embedded';
|
||||
import { Equivalence, _iterMap } from './flex';
|
||||
|
||||
export interface JsDictionary<V> {
|
||||
[key: string]: V;
|
||||
}
|
||||
|
||||
export namespace JsDictionary {
|
||||
export function isJsDictionary<V>(x: any): x is JsDictionary<V> {
|
||||
// We accept only literal objects and objects created via `new Object` as dictionaries.
|
||||
// Furthermore, we require no function-valued `__as_preserve__` property to exist.
|
||||
return typeof x === 'object'
|
||||
&& x !== null
|
||||
&& Object.getPrototypeOf(Object.getPrototypeOf(x)) === null
|
||||
&& typeof x.__as_preserve__ !== 'function'
|
||||
&& !isEmbedded(x);
|
||||
}
|
||||
|
||||
export function from<V>(entries: Iterable<[symbol, V]>): JsDictionary<V> {
|
||||
const r: JsDictionary<V> = {};
|
||||
for (const [key, value] of entries) r[key.description!] = value;
|
||||
return r;
|
||||
}
|
||||
|
||||
export function clear<V>(j: JsDictionary<V>): void {
|
||||
for (const key in j) delete j[key];
|
||||
}
|
||||
|
||||
export function remove<V>(j: JsDictionary<V>, key: symbol): boolean {
|
||||
const result = has(j, key);
|
||||
delete j[key.description!];
|
||||
return result;
|
||||
}
|
||||
|
||||
export function forEach<V>(
|
||||
j: JsDictionary<V>,
|
||||
callbackfn: (value: V, key: symbol) => void,
|
||||
): void {
|
||||
Object.entries(j).forEach(([key, val]) => callbackfn(val, Symbol.for(key)));
|
||||
}
|
||||
|
||||
export function get<V>(j: JsDictionary<V>, key: symbol): V | undefined {
|
||||
return j[key.description!];
|
||||
}
|
||||
|
||||
export function has<V>(j: JsDictionary<V>, key: symbol): boolean {
|
||||
return Object.hasOwnProperty.call(j, key.description!);
|
||||
}
|
||||
|
||||
export function set<V>(j: JsDictionary<V>, key: symbol, value: V): JsDictionary<V> {
|
||||
j[key.description!] = value;
|
||||
return j;
|
||||
}
|
||||
|
||||
export function size<V>(j: JsDictionary<V>): number {
|
||||
return Object.keys(j).length;
|
||||
}
|
||||
|
||||
export function entries<V>(j: JsDictionary<V>): IterableIterator<[symbol, V]> {
|
||||
return _iterMap(Object.entries(j).values(), ([k, v]) => [Symbol.for(k), v]);
|
||||
}
|
||||
|
||||
export function keys<V>(j: JsDictionary<V>): IterableIterator<symbol> {
|
||||
return _iterMap(Object.keys(j).values(), k => Symbol.for(k));
|
||||
}
|
||||
|
||||
export function values<V>(j: JsDictionary<V>): IterableIterator<V> {
|
||||
return Object.values(j).values();
|
||||
}
|
||||
|
||||
export function clone<V>(j: JsDictionary<V>): JsDictionary<V> {
|
||||
const r: JsDictionary<V> = {};
|
||||
Object.keys(j).forEach(k => r[k] = j[k]);
|
||||
return r;
|
||||
}
|
||||
|
||||
export function equals<V>(
|
||||
j1: JsDictionary<V>,
|
||||
j2: JsDictionary<V>,
|
||||
eqv: Equivalence<V> = (v1, v2) => v1 === v2,
|
||||
): boolean {
|
||||
if (size(j1) !== size(j2)) return false;
|
||||
for (let [k, v] of entries(j1)) {
|
||||
if (!has(j2, k)) return false;
|
||||
if (!eqv(v, get(j2, k)!)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -3,12 +3,15 @@ import { Bytes } from "./bytes";
|
|||
import { fold } from "./fold";
|
||||
import { is } from "./is";
|
||||
import { Value } from "./values";
|
||||
import { Set, Dictionary } from "./dictionary";
|
||||
import { Set, Dictionary, KeyedDictionary, DictionaryMap } from "./dictionary";
|
||||
import { Annotated } from "./annotated";
|
||||
import { unannotate } from "./strip";
|
||||
import { embed, isEmbedded, Embedded } from "./embedded";
|
||||
import { isEmbedded } from "./embedded";
|
||||
import { isCompound } from "./compound";
|
||||
import type { Embeddable } from "./embedded";
|
||||
import { JsDictionary } from "./jsdictionary";
|
||||
|
||||
export function merge<T>(
|
||||
export function merge<T extends Embeddable>(
|
||||
mergeEmbeddeds: (a: T, b: T) => T | undefined,
|
||||
item0: Value<T>,
|
||||
... items: Array<Value<T>>): Value<T>
|
||||
|
@ -18,10 +21,19 @@ export function merge<T>(
|
|||
}
|
||||
|
||||
function walk(a: Value<T>, b: Value<T>): Value<T> {
|
||||
if (a === b) return a;
|
||||
if (a === b) {
|
||||
// Shortcut for merges of trivially identical values.
|
||||
return a;
|
||||
}
|
||||
if (!isCompound(a) && !isCompound(b)) {
|
||||
// Don't do expensive recursive comparisons for compounds.
|
||||
if (is(a, b)) {
|
||||
// Shortcut for merges of marginally less trivially identical values.
|
||||
return a;
|
||||
}
|
||||
}
|
||||
return fold<T, Value<T>>(a, {
|
||||
boolean: die,
|
||||
single(_f: number) { return is(a, b) ? a : die(); },
|
||||
double(_f: number) { return is(a, b) ? a : die(); },
|
||||
integer: die,
|
||||
string: die,
|
||||
|
@ -32,33 +44,38 @@ export function merge<T>(
|
|||
if (!Record.isRecord<Value<T>, Tuple<Value<T>>, T>(b)) die();
|
||||
return Record(walk(r.label, b.label), walkMany(r, b));
|
||||
},
|
||||
|
||||
array(a: Array<Value<T>>) {
|
||||
if (!Array.isArray(b) || Record.isRecord(b)) die();
|
||||
return walkMany(a, b);
|
||||
},
|
||||
|
||||
set(_s: Set<T>) { die(); },
|
||||
dictionary(d: Dictionary<T>) {
|
||||
if (!Dictionary.isDictionary<T>(b)) die();
|
||||
const r = new Dictionary<T>();
|
||||
d.forEach((av,ak) => {
|
||||
const bv = b.get(ak);
|
||||
|
||||
dictionary(aMap: DictionaryMap<T>) {
|
||||
const bMap = Dictionary.asMap<T>(b);
|
||||
if (bMap === void 0) die();
|
||||
|
||||
const r = new DictionaryMap<T>();
|
||||
aMap.forEach((av,ak) => {
|
||||
const bv = bMap.get(ak);
|
||||
r.set(ak, bv === void 0 ? av : walk(av, bv));
|
||||
});
|
||||
b.forEach((bv, bk) => {
|
||||
if (!d.has(bk)) r.set(bk, bv);
|
||||
bMap.forEach((bv, bk) => {
|
||||
if (!aMap.has(bk)) r.set(bk, bv);
|
||||
});
|
||||
return r;
|
||||
return r.simplifiedValue();
|
||||
},
|
||||
|
||||
annotated(a: Annotated<T>) {
|
||||
return walk(a, unannotate(b));
|
||||
},
|
||||
|
||||
embedded(t: Embedded<T>) {
|
||||
embedded(t: T) {
|
||||
if (!isEmbedded<T>(b)) die();
|
||||
const r = mergeEmbeddeds(t.embeddedValue, b.embeddedValue);
|
||||
const r = mergeEmbeddeds(t, b);
|
||||
if (r === void 0) die();
|
||||
return embed(r);
|
||||
return r;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
import { Annotated } from './annotated';
|
||||
import { Bytes } from './bytes';
|
||||
import { Set, Dictionary } from './dictionary';
|
||||
import { Set, EncodableDictionary } from './dictionary';
|
||||
import { stringify } from './text';
|
||||
|
||||
import * as util from 'util';
|
||||
|
||||
[Bytes, Annotated, Set, Dictionary].forEach((C) => {
|
||||
[Bytes, Annotated, Set, EncodableDictionary].forEach((C) => {
|
||||
(C as any).prototype[util.inspect.custom] =
|
||||
function (_depth: any, _options: any) {
|
||||
return stringify(this, { indent: 2 });
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import { is, isAnnotated } from './is';
|
||||
import { Bytes } from './bytes';
|
||||
import { Set, Dictionary } from './dictionary';
|
||||
import { isEmbedded } from './embedded';
|
||||
import { Float } from './float';
|
||||
import { Value } from './values';
|
||||
import { Record } from './record';
|
||||
import type { Embeddable } from './embedded';
|
||||
|
||||
export function typeCode<T extends Embeddable>(v: Value<T>): number {
|
||||
if (isAnnotated<T>(v)) v = v.item;
|
||||
switch (typeof v) {
|
||||
case 'boolean':
|
||||
return 0;
|
||||
case 'number':
|
||||
case 'bigint':
|
||||
return 3;
|
||||
case 'string':
|
||||
return 4;
|
||||
case 'symbol':
|
||||
return 6;
|
||||
case 'object':
|
||||
if (Float.isFloat(v)) return 2; // 1 was for single-precision floats
|
||||
if (Bytes.isBytes(v)) return 5;
|
||||
if (Array.isArray(v)) {
|
||||
return ('label' in v) ? 7 : 8;
|
||||
}
|
||||
if (Set.isSet<T>(v)) return 9;
|
||||
if (Dictionary.isDictionary<T>(v)) return 10;
|
||||
if (isEmbedded(v)) return 11;
|
||||
/* fall through */
|
||||
default:
|
||||
throw new Error("Invalid Value<T> in typeCode");
|
||||
}
|
||||
}
|
||||
|
||||
export function compare<T extends Embeddable>(
|
||||
a: Value<T>,
|
||||
b: Value<T>,
|
||||
compare_embedded: (a: T, b: T) => number = (a, b) => is(a, b) ? 0 : a < b ? -1 : 1,
|
||||
): number {
|
||||
function cmp(a: Value<T>, b: Value<T>): number {
|
||||
if (isAnnotated<T>(a)) a = a.item;
|
||||
if (isAnnotated<T>(b)) b = b.item;
|
||||
const ta = typeCode(a);
|
||||
const tb = typeCode(b);
|
||||
if (ta < tb) return -1;
|
||||
if (ta > tb) return 1;
|
||||
switch (ta) {
|
||||
case 0:
|
||||
case 3:
|
||||
case 4: {
|
||||
const va = a as any;
|
||||
const vb = b as any;
|
||||
return va < vb ? -1 : va > vb ? 1 : 0;
|
||||
}
|
||||
// case 1: // was single-precision
|
||||
case 2: {
|
||||
const va = (a as Float).value;
|
||||
const vb = (b as Float).value;
|
||||
return va < vb ? -1 : va > vb ? 1 : 0;
|
||||
}
|
||||
case 5:
|
||||
return Bytes.compare(a as Bytes, b as Bytes);
|
||||
case 6: {
|
||||
const va = (a as symbol).description!;
|
||||
const vb = (b as symbol).description!;
|
||||
return va < vb ? -1 : va > vb ? 1 : 0;
|
||||
}
|
||||
case 7: {
|
||||
const lr = cmp((a as Record<Value<T>, Value<T>[], T>).label,
|
||||
(b as Record<Value<T>, Value<T>[], T>).label);
|
||||
if (lr !== 0) return lr;
|
||||
/* fall through */
|
||||
}
|
||||
case 8: {
|
||||
const va = a as Value<T>[];
|
||||
const vb = b as Value<T>[];
|
||||
const l = Math.min(va.length, vb.length)
|
||||
for (let i = 0; i < l; i++) {
|
||||
const c = cmp(va[i], vb[i]);
|
||||
if (c !== 0) return c;
|
||||
}
|
||||
return va.length < vb.length ? -1 : va.length > vb.length ? 1 : 0;
|
||||
}
|
||||
case 9: {
|
||||
const va = Array.from(a as Set<T>).sort(cmp);
|
||||
const vb = Array.from(b as Set<T>).sort(cmp);
|
||||
return cmp(va, vb);
|
||||
}
|
||||
case 10: {
|
||||
const va = Array.from(Dictionary.asMap<T>(a)!.entries()).sort(cmp);
|
||||
const vb = Array.from(Dictionary.asMap<T>(b)!.entries()).sort(cmp);
|
||||
return cmp(va, vb);
|
||||
}
|
||||
case 11:
|
||||
return compare_embedded(a as T, b as T);
|
||||
default:
|
||||
throw new Error("Invalid typeCode: " + ta);
|
||||
}
|
||||
}
|
||||
return cmp(a, b);
|
||||
}
|
|
@ -0,0 +1,353 @@
|
|||
// Preserves-Expressions. https://preserves.dev/preserves-expressions.html
|
||||
|
||||
import { ReaderBase } from './reader';
|
||||
import { Atom, Value } from './values';
|
||||
import { Position, annotate, formatPosition } from './annotated';
|
||||
import { Record as VRecord } from './record';
|
||||
import { Embeddable, GenericEmbedded } from './embedded';
|
||||
import { fromJS } from './fromjs';
|
||||
import { DictionaryMap, Set as VSet } from './dictionary';
|
||||
|
||||
export type Expr = SimpleExpr | Punct;
|
||||
export type SimpleExpr = Atom | Compound | Embedded;
|
||||
|
||||
export type Positioned<I> = { position: Position, item: I, annotations?: Annotations };
|
||||
|
||||
export class Punct {
|
||||
constructor(public text: string) {}
|
||||
__as_preserve__(): Value { return VRecord(Symbol.for('p'), [Symbol.for(this.text)]); }
|
||||
|
||||
isComma(): boolean { return this.text === ','; }
|
||||
static isComma(v: Expr): boolean { return v instanceof Punct && v.isComma(); }
|
||||
|
||||
isColon(n = 1): boolean { return this.text === ':'.repeat(n); }
|
||||
static isColon(v: Expr, n = 1): boolean { return v instanceof Punct && v.isColon(n); }
|
||||
}
|
||||
|
||||
export class Embedded {
|
||||
constructor(public expr: SimpleExpr, public annotations?: Annotations) {}
|
||||
__as_preserve__(): Value {
|
||||
const v = fromJS(this.expr);
|
||||
return new GenericEmbedded(this.annotations?.wrap(v) ?? v);
|
||||
}
|
||||
}
|
||||
|
||||
export class BaseCompound<I> {
|
||||
positions: Position[] = [];
|
||||
exprs: I[] = [];
|
||||
annotations?: Annotations[] = void 0; // sparse array when non-void
|
||||
|
||||
get(i: number): Positioned<I> | undefined {
|
||||
if (i >= this.exprs.length) return void 0;
|
||||
return {
|
||||
position: this.positions[i],
|
||||
item: this.exprs[i],
|
||||
annotations: this.annotations && this.annotations[i],
|
||||
};
|
||||
}
|
||||
|
||||
push(p: Positioned<I>): true;
|
||||
push(expr: I, position: Position, annotations?: Annotations): true;
|
||||
push(v: Positioned<I> | I, position?: Position, annotations?: Annotations) {
|
||||
if (position === void 0) {
|
||||
const p = v as Positioned<I>;
|
||||
if (p.annotations) this._ensureAnnotations()[this.exprs.length] = p.annotations;
|
||||
this.positions.push(p.position);
|
||||
this.exprs.push(p.item);
|
||||
} else {
|
||||
if (annotations) this._ensureAnnotations()[this.exprs.length] = annotations;
|
||||
this.positions.push(position);
|
||||
this.exprs.push(v as I);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_ensureAnnotations(): Annotations[] {
|
||||
if (this.annotations === void 0) this.annotations = [];
|
||||
return this.annotations;
|
||||
}
|
||||
|
||||
_annotationsAt(index: number): Annotations {
|
||||
return this._ensureAnnotations()[index] ??= new Annotations();
|
||||
}
|
||||
|
||||
preservesValues(): Value[] {
|
||||
return this.exprs.map((p, i) => {
|
||||
const v = fromJS(p);
|
||||
if (this.annotations?.[i] !== void 0) {
|
||||
return this.annotations[i].wrap(v);
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
__as_preserve__(): Value {
|
||||
return this.preservesValues();
|
||||
}
|
||||
|
||||
map<R>(f: (item: Positioned<I>, index: number) => R, offset = 0): R[] {
|
||||
const result: R[] = [];
|
||||
for (let i = offset; i < this.exprs.length; i++) {
|
||||
result.push(f(this.get(i)!, i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[Symbol.iterator](): IterableIterator<Positioned<I>> {
|
||||
let c = this;
|
||||
let i = 0;
|
||||
return {
|
||||
next(): IteratorResult<Positioned<I>> {
|
||||
if (i < c.exprs.length) {
|
||||
return { done: false, value: c.get(i++)! };
|
||||
} else {
|
||||
return { done: true, value: void 0 };
|
||||
}
|
||||
},
|
||||
[Symbol.iterator]() { return c[Symbol.iterator](); }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class Document extends BaseCompound<Expr> {}
|
||||
|
||||
export class Annotations extends BaseCompound<SimpleExpr> {
|
||||
wrap(v: Value): Value {
|
||||
return annotate(v, ... this.preservesValues());
|
||||
}
|
||||
}
|
||||
|
||||
export type CompoundVariant = 'sequence' | 'record' | 'block' | 'group' | 'set';
|
||||
|
||||
export abstract class Compound extends BaseCompound<Expr> {
|
||||
abstract get variant(): CompoundVariant;
|
||||
__as_preserve__(): Value {
|
||||
const vs = this.preservesValues();
|
||||
switch (this.variant) {
|
||||
case 'sequence': return vs;
|
||||
case 'record': return VRecord(Symbol.for('r'), vs);
|
||||
case 'block': return VRecord(Symbol.for('b'), vs);
|
||||
case 'group': return VRecord(Symbol.for('g'), vs);
|
||||
case 'set': return VRecord(Symbol.for('s'), vs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Sequence extends Compound {
|
||||
get variant(): CompoundVariant { return 'sequence'; }
|
||||
}
|
||||
|
||||
export class Record extends Compound {
|
||||
get variant(): CompoundVariant { return 'record'; }
|
||||
}
|
||||
|
||||
export class Block extends Compound {
|
||||
get variant(): CompoundVariant { return 'block'; }
|
||||
}
|
||||
|
||||
export class Group extends Compound {
|
||||
get variant(): CompoundVariant { return 'group'; }
|
||||
}
|
||||
|
||||
export class Set extends Compound {
|
||||
get variant(): CompoundVariant { return 'set'; }
|
||||
}
|
||||
|
||||
export class Reader extends ReaderBase<never> {
|
||||
nextDocument(howMany: 'all' | 'one' = 'all'): Document {
|
||||
const doc = new Document();
|
||||
this.readExpr(doc);
|
||||
if (howMany === 'all') {
|
||||
while (this.readExpr(doc)) {}
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
readCompound(c: Compound, terminator: string): Compound {
|
||||
while (this.readExpr(c, terminator)) {}
|
||||
return c;
|
||||
}
|
||||
|
||||
readSimpleExpr(c: BaseCompound<SimpleExpr>): boolean {
|
||||
return this._readInto(c, false);
|
||||
}
|
||||
|
||||
readExpr(c: BaseCompound<Expr>, terminator: string | null = null): boolean {
|
||||
return this._readInto(c as BaseCompound<SimpleExpr> /* yuck */, true, terminator);
|
||||
}
|
||||
|
||||
_checkTerminator(actual: string, expected: string | null, startPos: Position): false {
|
||||
if (actual === expected) return false;
|
||||
this.state.error('Unexpected ' + actual, startPos);
|
||||
}
|
||||
|
||||
_readInto(c: BaseCompound<SimpleExpr>, acceptPunct: boolean, terminator: string | null = null): boolean {
|
||||
while (true) {
|
||||
this.state.skipws();
|
||||
if (this.state.atEnd() && terminator === null) return false;
|
||||
const startPos = this.state.copyPos();
|
||||
const ch = this.state.nextchar();
|
||||
switch (ch) {
|
||||
case '"':
|
||||
return c.push(this.state.readString('"'), startPos);
|
||||
case '|':
|
||||
return c.push(Symbol.for(this.state.readString('|')), startPos);
|
||||
case ';':
|
||||
if (acceptPunct) {
|
||||
return (c as BaseCompound<Expr>).push(new Punct(';'), startPos);
|
||||
} else {
|
||||
this.state.error('Semicolon is not permitted at this location', startPos);
|
||||
}
|
||||
case '@':
|
||||
if (!this.readSimpleExpr(c._annotationsAt(c.exprs.length))) {
|
||||
this.state.error('Missing annotation', startPos);
|
||||
}
|
||||
continue;
|
||||
case ':': {
|
||||
let colons: string = ch;
|
||||
while (!this.state.atEnd() && this.state.peek() === ':') {
|
||||
colons = colons + ':';
|
||||
this.state.advance();
|
||||
}
|
||||
if (acceptPunct) {
|
||||
return (c as BaseCompound<Expr>).push(new Punct(colons), startPos);
|
||||
} else {
|
||||
this.state.error('Colons are not permitted at this location', startPos);
|
||||
}
|
||||
}
|
||||
case '#': {
|
||||
const ch = this.state.nextchar();
|
||||
switch (ch) {
|
||||
case ' ': case '\t': {
|
||||
const here = this.state.copyPos();
|
||||
c._annotationsAt(c.exprs.length).push(this.state.readCommentLine(), here);
|
||||
continue;
|
||||
}
|
||||
case '\n': case '\r': {
|
||||
const here = this.state.copyPos();
|
||||
c._annotationsAt(c.exprs.length).push('', here);
|
||||
continue;
|
||||
}
|
||||
case '!': {
|
||||
const here = this.state.copyPos();
|
||||
const r = new Record();
|
||||
r.push(Symbol.for('interpreter'), here);
|
||||
r.push(this.state.readCommentLine(), here);
|
||||
c._annotationsAt(c.exprs.length).push(r, here);
|
||||
continue;
|
||||
}
|
||||
case 'f': this.state.requireDelimiter('#f'); return c.push(false, startPos);
|
||||
case 't': this.state.requireDelimiter('#t'); return c.push(true, startPos);
|
||||
case '{': return c.push(this.readCompound(new Set(), '}'), startPos);
|
||||
case '"': return c.push(this.state.readLiteralBinary(), startPos);
|
||||
case 'x': switch (this.state.nextchar()) {
|
||||
case '"': return c.push(this.state.readHexBinary(), startPos);
|
||||
case 'd': return c.push(this.state.readHexFloat(), startPos);
|
||||
default: this.state.error('Invalid #x syntax', startPos);
|
||||
}
|
||||
case '[': return c.push(this.state.readBase64Binary(), startPos);
|
||||
case ':': {
|
||||
const r = new BaseCompound<SimpleExpr>();
|
||||
if (!this.readSimpleExpr(r)) return false;
|
||||
const e = new Embedded(r.exprs[0], r.annotations && r.annotations[0]);
|
||||
return c.push(e, startPos);
|
||||
}
|
||||
default:
|
||||
this.state.error(`Invalid # syntax: ${ch}`, startPos);
|
||||
}
|
||||
}
|
||||
case '(': return c.push(this.readCompound(new Group(), ')'), startPos);
|
||||
case '<': return c.push(this.readCompound(new Record(), '>'), startPos);
|
||||
case '[': return c.push(this.readCompound(new Sequence(), ']'), startPos);
|
||||
case '{': return c.push(this.readCompound(new Block(), '}'), startPos);
|
||||
case ')': return this._checkTerminator(ch, terminator, startPos);
|
||||
case '>': return this._checkTerminator(ch, terminator, startPos);
|
||||
case ']': return this._checkTerminator(ch, terminator, startPos);
|
||||
case '}': return this._checkTerminator(ch, terminator, startPos);
|
||||
case ',':
|
||||
if (acceptPunct) {
|
||||
return (c as BaseCompound<Expr>).push(new Punct(','), startPos);
|
||||
} else {
|
||||
this.state.error('Comma is not permitted at this location', startPos);
|
||||
}
|
||||
default:
|
||||
return c.push(this.state.readRawSymbolOrNumber(ch), startPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface AsPreservesOptions<T extends Embeddable> {
|
||||
onGroup?: (g: Positioned<Group>) => Value<T>;
|
||||
onEmbedded?: (e: Positioned<Expr>, walk: (p: Positioned<Expr>) => Value<T>) => Value<T>;
|
||||
error?: (tag: string, position: Position) => Value<T>;
|
||||
}
|
||||
|
||||
export function asPreserves<T extends Embeddable>(
|
||||
p: Positioned<Expr>,
|
||||
options: AsPreservesOptions<T> = {},
|
||||
): Value<T> {
|
||||
const error = options.error ?? ((tag, position) => {
|
||||
throw new Error(formatPosition(position) + ": " + tag);
|
||||
});
|
||||
|
||||
function nonCommas(p: Compound): Positioned<Expr>[] {
|
||||
return Array.from(p).filter(p => !Punct.isComma(p.item));
|
||||
}
|
||||
|
||||
function walk(p: Positioned<Expr>): Value<T> {
|
||||
if (p.item instanceof Punct) {
|
||||
return error('invalid-punctuation', p.position);
|
||||
} else if (p.item instanceof Embedded) {
|
||||
if (options.onEmbedded) {
|
||||
return options.onEmbedded({ position: p.position, item: p.item.expr }, walk);
|
||||
} else {
|
||||
return error('unexpected-embedded', p.position);
|
||||
}
|
||||
} else if (p.item instanceof Compound) {
|
||||
switch (p.item.variant) {
|
||||
case 'sequence':
|
||||
return nonCommas(p.item).map(walk);
|
||||
case 'record': {
|
||||
const vs = nonCommas(p.item).map(walk);
|
||||
if (vs.length < 1) {
|
||||
return error('invalid-record', p.position);
|
||||
}
|
||||
const r = vs.slice(1) as unknown as VRecord<Value<T>, Value<T>[], T>;
|
||||
r.label = vs[0];
|
||||
return r;
|
||||
}
|
||||
case 'block': {
|
||||
const d = new DictionaryMap<T>();
|
||||
const vs = nonCommas(p.item);
|
||||
if ((vs.length % 3) !== 0) {
|
||||
return error('invalid-dictionary', p.position);
|
||||
}
|
||||
for (let i = 0; i < vs.length; i += 3) {
|
||||
if (!Punct.isColon(vs[i + 1].item)) {
|
||||
return error('missing-colon', vs[i + 1].position);
|
||||
}
|
||||
const k = walk(vs[i]);
|
||||
const v = walk(vs[i + 2]);
|
||||
d.set(k, v);
|
||||
}
|
||||
return d.simplifiedValue();
|
||||
}
|
||||
case 'group': {
|
||||
if (options.onGroup) {
|
||||
return options.onGroup(p as Positioned<Group>);
|
||||
} else {
|
||||
return error('unexpected-group', p.position);
|
||||
}
|
||||
}
|
||||
case 'set':
|
||||
return new VSet(nonCommas(p.item).map(walk));
|
||||
}
|
||||
} else {
|
||||
return p.item;
|
||||
}
|
||||
}
|
||||
|
||||
return walk(p);
|
||||
}
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
import type { Value } from './values';
|
||||
import { DecodeError, ShortPacket } from './codec';
|
||||
import { Dictionary, Set } from './dictionary';
|
||||
import { Dictionary, DictionaryMap, Set } from './dictionary';
|
||||
import { strip } from './strip';
|
||||
import { Bytes, underlying, unhexDigit } from './bytes';
|
||||
import { Bytes, unhexDigit } from './bytes';
|
||||
import { Decoder, DecoderState, neverEmbeddedTypeDecode } from './decoder';
|
||||
import { Record } from './record';
|
||||
import { Annotated, newPosition, Position, updatePosition } from './annotated';
|
||||
import { Double, DoubleFloat, FloatType, Single, SingleFloat } from './float';
|
||||
import { Double, DoubleFloat } from './float';
|
||||
import { stringify } from './text';
|
||||
import { embed, GenericEmbedded, EmbeddedTypeDecode } from './embedded';
|
||||
import { Embeddable, GenericEmbedded, EmbeddedTypeDecode } from './embedded';
|
||||
|
||||
export interface ReaderStateOptions {
|
||||
includeAnnotations?: boolean;
|
||||
|
@ -21,16 +21,13 @@ export interface ReaderOptions<T> extends ReaderStateOptions {
|
|||
embeddedDecode?: EmbeddedTypeDecode<T>;
|
||||
}
|
||||
|
||||
type IntOrFloat = 'int' | 'float';
|
||||
type Numeric = number | SingleFloat | DoubleFloat;
|
||||
type IntContinuation = (kind: IntOrFloat, acc: string) => Numeric;
|
||||
const MAX_SAFE_INTEGERn = BigInt(Number.MAX_SAFE_INTEGER);
|
||||
const MIN_SAFE_INTEGERn = BigInt(Number.MIN_SAFE_INTEGER);
|
||||
|
||||
export const NUMBER_RE: RegExp = /^([-+]?\d+)(((\.\d+([eE][-+]?\d+)?)|([eE][-+]?\d+))([fF]?))?$/;
|
||||
export const NUMBER_RE: RegExp = /^([-+]?\d+)((\.\d+([eE][-+]?\d+)?)|([eE][-+]?\d+))?$/;
|
||||
// Groups:
|
||||
// 1 - integer part and sign
|
||||
// 2 - decimal part, exponent and Float marker
|
||||
// 3 - decimal part and exponent
|
||||
// 7 - Float marker
|
||||
// 2 - decimal part and exponent
|
||||
|
||||
export class ReaderState {
|
||||
buffer: string;
|
||||
|
@ -97,25 +94,26 @@ export class ReaderState {
|
|||
return this.buffer.charCodeAt(this.advance());
|
||||
}
|
||||
|
||||
skipws() {
|
||||
skipws(skipCommas = false) {
|
||||
while (true) {
|
||||
if (this.atEnd()) break;
|
||||
if (!isSpace(this.peek())) break;
|
||||
const c = this.peek();
|
||||
if (!(isSpace(c) || (skipCommas && c === ','))) break;
|
||||
this.advance();
|
||||
}
|
||||
}
|
||||
|
||||
readHex2(): number {
|
||||
const x1 = unhexDigit(this.nextcharcode());
|
||||
const x2 = unhexDigit(this.nextcharcode());
|
||||
const x1 = unhexDigit(this.nextcharcode(), DecodeError);
|
||||
const x2 = unhexDigit(this.nextcharcode(), DecodeError);
|
||||
return (x1 << 4) | x2;
|
||||
}
|
||||
|
||||
readHex4(): number {
|
||||
const x1 = unhexDigit(this.nextcharcode());
|
||||
const x2 = unhexDigit(this.nextcharcode());
|
||||
const x3 = unhexDigit(this.nextcharcode());
|
||||
const x4 = unhexDigit(this.nextcharcode());
|
||||
const x1 = unhexDigit(this.nextcharcode(), DecodeError);
|
||||
const x2 = unhexDigit(this.nextcharcode(), DecodeError);
|
||||
const x3 = unhexDigit(this.nextcharcode(), DecodeError);
|
||||
const x4 = unhexDigit(this.nextcharcode(), DecodeError);
|
||||
return (x1 << 12) | (x2 << 8) | (x3 << 4) | x4;
|
||||
}
|
||||
|
||||
|
@ -131,20 +129,14 @@ export class ReaderState {
|
|||
}
|
||||
}
|
||||
|
||||
readHexFloat(precision: FloatType): SingleFloat | DoubleFloat {
|
||||
readHexFloat(): DoubleFloat {
|
||||
const pos = this.copyPos();
|
||||
if (this.nextchar() !== '"') {
|
||||
this.error("Missing open-double-quote in hex-encoded floating-point number", pos);
|
||||
}
|
||||
const bs = this.readHexBinary();
|
||||
switch (precision) {
|
||||
case 'Single':
|
||||
if (bs.length !== 4) this.error("Incorrect number of bytes in hex-encoded Float", pos);
|
||||
return SingleFloat.fromBytes(bs);
|
||||
case 'Double':
|
||||
if (bs.length !== 8) this.error("Incorrect number of bytes in hex-encoded Double", pos);
|
||||
return DoubleFloat.fromBytes(bs);
|
||||
}
|
||||
if (bs.length !== 8) this.error("Incorrect number of bytes in hex-encoded Double", pos);
|
||||
return DoubleFloat.fromBytes(bs);
|
||||
}
|
||||
|
||||
readBase64Binary(): Bytes {
|
||||
|
@ -155,27 +147,35 @@ export class ReaderState {
|
|||
if (c === ']') break;
|
||||
acc = acc + c;
|
||||
}
|
||||
return decodeBase64(acc);
|
||||
return Bytes.fromBase64(acc);
|
||||
}
|
||||
|
||||
readRawSymbolOrNumber<T>(acc: string): Value<T> {
|
||||
while (true) {
|
||||
if (this.atEnd()) break;
|
||||
const ch = this.peek();
|
||||
if (('(){}[]<>";,@#:|'.indexOf(ch) !== -1) || isSpace(ch)) break;
|
||||
this.advance();
|
||||
acc = acc + ch;
|
||||
}
|
||||
requireDelimiter(prefix: string): void {
|
||||
if (this.delimiterFollows()) return;
|
||||
this.error(`Delimiter must follow ${prefix}`, this.pos);
|
||||
}
|
||||
|
||||
static readonly DELIMITERS = '(){}[]<>";,@#:|';
|
||||
|
||||
delimiterFollows(): boolean {
|
||||
if (this.atEnd()) return true;
|
||||
const ch = this.peek();
|
||||
return (ReaderState.DELIMITERS.indexOf(ch) !== -1) || isSpace(ch);
|
||||
}
|
||||
|
||||
readRawSymbolOrNumber(acc: string): number | bigint | symbol | DoubleFloat {
|
||||
while (!this.delimiterFollows()) acc = acc + this.nextchar();
|
||||
const m = NUMBER_RE.exec(acc);
|
||||
if (m) {
|
||||
if (m[2] === void 0) {
|
||||
let v = parseInt(m[1]);
|
||||
if (Object.is(v, -0)) v = 0;
|
||||
return v;
|
||||
} else if (m[7] === '') {
|
||||
return Double(parseFloat(m[1] + m[3]));
|
||||
let v = BigInt(m[1]);
|
||||
if (v <= MIN_SAFE_INTEGERn || v >= MAX_SAFE_INTEGERn) {
|
||||
return v;
|
||||
} else {
|
||||
return Number(v);
|
||||
}
|
||||
} else {
|
||||
return Single(parseFloat(m[1] + m[3]));
|
||||
return Double(parseFloat(acc));
|
||||
}
|
||||
} else {
|
||||
return Symbol.for(acc);
|
||||
|
@ -250,6 +250,16 @@ export class ReaderState {
|
|||
'x',
|
||||
() => this.readHex2());
|
||||
}
|
||||
|
||||
readCommentLine(): string {
|
||||
let acc = '';
|
||||
while (true) {
|
||||
if (this.atEnd()) return acc;
|
||||
const c = this.nextchar();
|
||||
if (c === '\n' || c === '\r') return acc;
|
||||
acc = acc + c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const genericEmbeddedTypeDecode: EmbeddedTypeDecode<GenericEmbedded> = {
|
||||
|
@ -262,7 +272,7 @@ export const genericEmbeddedTypeDecode: EmbeddedTypeDecode<GenericEmbedded> = {
|
|||
},
|
||||
};
|
||||
|
||||
export class Reader<T> {
|
||||
export class ReaderBase<T extends Embeddable> {
|
||||
state: ReaderState;
|
||||
embeddedType: EmbeddedTypeDecode<T>;
|
||||
|
||||
|
@ -285,17 +295,13 @@ export class Reader<T> {
|
|||
write(data: string) {
|
||||
this.state.write(data);
|
||||
}
|
||||
}
|
||||
|
||||
export class Reader<T extends Embeddable> extends ReaderBase<T> {
|
||||
|
||||
readCommentLine(): Value<T> {
|
||||
const startPos = this.state.copyPos();
|
||||
let acc = '';
|
||||
while (true) {
|
||||
const c = this.state.nextchar();
|
||||
if (c === '\n' || c === '\r') {
|
||||
return this.wrap(acc, startPos);
|
||||
}
|
||||
acc = acc + c;
|
||||
}
|
||||
return this.wrap(this.state.readCommentLine(), startPos);
|
||||
}
|
||||
|
||||
wrap(v: Value<T>, pos: Position): Value<T> {
|
||||
|
@ -336,7 +342,7 @@ export class Reader<T> {
|
|||
case '|':
|
||||
return Symbol.for(this.state.readString('|'));
|
||||
case ';':
|
||||
return this.annotateNextWith(this.readCommentLine());
|
||||
this.state.error('Semicolon is reserved syntax', startPos);
|
||||
case '@':
|
||||
return this.annotateNextWith(this.next());
|
||||
case ':':
|
||||
|
@ -344,34 +350,39 @@ export class Reader<T> {
|
|||
case '#': {
|
||||
const c = this.state.nextchar();
|
||||
switch (c) {
|
||||
case 'f': return false;
|
||||
case 't': return true;
|
||||
case '{': return this.seq(new Set<T>(), (v, s) => s.add(v), '}');
|
||||
case ' ': case '\t': return this.annotateNextWith(this.readCommentLine());
|
||||
case '\n': case '\r': return this.annotateNextWith('');
|
||||
case '!':
|
||||
return this.annotateNextWith(
|
||||
Record(Symbol.for('interpreter'), [this.readCommentLine()]));
|
||||
case 'f': this.state.requireDelimiter('#f'); return false;
|
||||
case 't': this.state.requireDelimiter('#t'); return true;
|
||||
case '{': return this.readSet();
|
||||
case '"': return this.state.readLiteralBinary();
|
||||
case 'x': switch (this.state.nextchar()) {
|
||||
case '"': return this.state.readHexBinary();
|
||||
case 'f': return this.state.readHexFloat('Single');
|
||||
case 'd': return this.state.readHexFloat('Double');
|
||||
case 'd': return this.state.readHexFloat();
|
||||
default: this.state.error('Invalid #x syntax', startPos);
|
||||
}
|
||||
case '[': return this.state.readBase64Binary();
|
||||
case '!': return embed(this.embeddedType.fromValue(
|
||||
case ':': return this.embeddedType.fromValue(
|
||||
new Reader<GenericEmbedded>(this.state, genericEmbeddedTypeDecode).next(),
|
||||
this.state.options));
|
||||
this.state.options);
|
||||
default:
|
||||
this.state.error(`Invalid # syntax: ${c}`, startPos);
|
||||
}
|
||||
}
|
||||
case '<': {
|
||||
const label = this.next();
|
||||
const fields = this.readSequence('>');
|
||||
const fields = this.readSequence('>', false);
|
||||
return Record(label, fields);
|
||||
}
|
||||
case '[': return this.readSequence(']');
|
||||
case '[': return this.readSequence(']', true);
|
||||
case '{': return this.readDictionary();
|
||||
case '>': this.state.error('Unexpected >', startPos);
|
||||
case ']': this.state.error('Unexpected ]', startPos);
|
||||
case '}': this.state.error('Unexpected }', startPos);
|
||||
case ',': this.state.error('Unexpected ,', startPos);
|
||||
default:
|
||||
return this.state.readRawSymbolOrNumber(c);
|
||||
}
|
||||
|
@ -379,9 +390,9 @@ export class Reader<T> {
|
|||
return this.wrap(unwrapped, startPos);
|
||||
}
|
||||
|
||||
seq<S>(acc: S, update: (v: Value<T>, acc: S) => void, ch: string): S {
|
||||
seq<S>(skipCommas: boolean, acc: S, update: (v: Value<T>, acc: S) => void, ch: string): S {
|
||||
while (true) {
|
||||
this.state.skipws();
|
||||
this.state.skipws(skipCommas);
|
||||
if (this.state.peek() === ch) {
|
||||
this.state.advance();
|
||||
return acc;
|
||||
|
@ -390,54 +401,37 @@ export class Reader<T> {
|
|||
}
|
||||
}
|
||||
|
||||
readSequence(ch: string): Array<Value<T>> {
|
||||
return this.seq([] as Array<Value<T>>, (v, acc) => acc.push(v), ch);
|
||||
readSequence(ch: string, skipCommas: boolean): Array<Value<T>> {
|
||||
return this.seq(skipCommas, [] as Array<Value<T>>, (v, acc) => acc.push(v), ch);
|
||||
}
|
||||
|
||||
readDictionary(): Dictionary<T> {
|
||||
return this.seq(new Dictionary<T>(),
|
||||
(k, acc) => {
|
||||
this.state.skipws();
|
||||
switch (this.state.peek()) {
|
||||
case ':':
|
||||
if (acc.has(k)) this.state.error(
|
||||
`Duplicate key: ${stringify(k)}`, this.state.pos);
|
||||
this.state.advance();
|
||||
acc.set(k, this.next());
|
||||
break;
|
||||
default:
|
||||
this.state.error('Missing key/value separator', this.state.pos);
|
||||
}
|
||||
return this.seq(true, new DictionaryMap<T>(), (k, acc) => {
|
||||
this.state.skipws();
|
||||
switch (this.state.peek()) {
|
||||
case ':':
|
||||
this.state.advance();
|
||||
if (acc.has(k)) this.state.error(`Duplicate key: ${stringify(k)}`, this.state.pos);
|
||||
acc.set(k, this.next());
|
||||
break;
|
||||
default:
|
||||
this.state.error('Missing key/value separator', this.state.pos);
|
||||
}
|
||||
}, '}').simplifiedValue();
|
||||
}
|
||||
|
||||
readSet(): Set<T> {
|
||||
return this.seq(true,
|
||||
new Set<T>(),
|
||||
(v, acc) => {
|
||||
if (acc.has(v)) this.state.error(
|
||||
`Duplicate value in set: ${stringify(v)}`, this.state.pos);
|
||||
acc.add(v);
|
||||
},
|
||||
'}');
|
||||
}
|
||||
}
|
||||
|
||||
const BASE64: {[key: string]: number} = {};
|
||||
[... 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'].forEach(
|
||||
(c, i) => BASE64[c] = i);
|
||||
BASE64['+'] = BASE64['-'] = 62;
|
||||
BASE64['/'] = BASE64['_'] = 63;
|
||||
|
||||
export function decodeBase64(s: string): Bytes {
|
||||
const bs = new Uint8Array(Math.floor(s.length * 3/4));
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
while (i < s.length) {
|
||||
const v1 = BASE64[s[i++]];
|
||||
const v2 = BASE64[s[i++]];
|
||||
const v3 = BASE64[s[i++]];
|
||||
const v4 = BASE64[s[i++]];
|
||||
const v = (v1 << 18) | (v2 << 12) | (v3 << 6) | v4;
|
||||
bs[j++] = (v >> 16) & 255;
|
||||
if (v3 === void 0) break;
|
||||
bs[j++] = (v >> 8) & 255;
|
||||
if (v4 === void 0) break;
|
||||
bs[j++] = v & 255;
|
||||
}
|
||||
return Bytes.from(bs.subarray(0, j));
|
||||
}
|
||||
|
||||
function isSpace(s: string): boolean {
|
||||
return ' \t\n\r,'.indexOf(s) !== -1;
|
||||
return ' \t\n\r'.indexOf(s) !== -1;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { GenericEmbedded } from "./embedded";
|
||||
import { Embeddable, GenericEmbedded } from "./embedded";
|
||||
import { is } from "./is";
|
||||
import { Value } from "./values";
|
||||
import { Writer } from "./writer";
|
||||
|
||||
export type Tuple<T> = Array<T> | [T];
|
||||
|
||||
export type Record<LabelType extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>
|
||||
export type Record<LabelType extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends Embeddable = GenericEmbedded>
|
||||
= FieldsType & { label: LabelType };
|
||||
|
||||
export type RecordGetters<Fs, R> = {
|
||||
|
@ -15,7 +14,7 @@ export type RecordGetters<Fs, R> = {
|
|||
export type CtorTypes<Fs, Names extends Tuple<keyof Fs>> =
|
||||
{ [K in keyof Names]: Fs[keyof Fs & Names[K]] } & any[];
|
||||
|
||||
export interface RecordConstructor<L extends Value<T>, Fs, Names extends Tuple<keyof Fs>, T = GenericEmbedded> {
|
||||
export interface RecordConstructor<L extends Value<T>, Fs, Names extends Tuple<keyof Fs>, T extends Embeddable = GenericEmbedded> {
|
||||
(...fields: CtorTypes<Fs, Names>): Record<L, CtorTypes<Fs, Names>, T>;
|
||||
constructorInfo: RecordConstructorInfo<L, T>;
|
||||
isClassOf(v: any): v is Record<L, CtorTypes<Fs, Names>, T>;
|
||||
|
@ -23,7 +22,7 @@ export interface RecordConstructor<L extends Value<T>, Fs, Names extends Tuple<k
|
|||
fieldNumbers: { [K in string & keyof Fs]: number };
|
||||
};
|
||||
|
||||
export interface RecordConstructorInfo<L extends Value<T>, T = GenericEmbedded> {
|
||||
export interface RecordConstructorInfo<L extends Value<T>, T extends Embeddable = GenericEmbedded> {
|
||||
label: L;
|
||||
arity: number;
|
||||
}
|
||||
|
@ -48,23 +47,23 @@ export function Record<L, FieldsType extends Tuple<any>>(
|
|||
}
|
||||
|
||||
export namespace Record {
|
||||
export function isRecord<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>(x: any): x is Record<L, FieldsType, T> {
|
||||
export function isRecord<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends Embeddable = GenericEmbedded>(x: any): x is Record<L, FieldsType, T> {
|
||||
return Array.isArray(x) && 'label' in x;
|
||||
}
|
||||
|
||||
export function constructorInfo<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>(
|
||||
export function constructorInfo<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends Embeddable = GenericEmbedded>(
|
||||
r: Record<L, FieldsType, T>): RecordConstructorInfo<L, T>
|
||||
{
|
||||
return { label: r.label, arity: r.length };
|
||||
}
|
||||
|
||||
export function isClassOf<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T = GenericEmbedded>(
|
||||
export function isClassOf<L extends Value<T>, FieldsType extends Tuple<Value<T>>, T extends Embeddable = GenericEmbedded>(
|
||||
ci: RecordConstructorInfo<L, T>, v: any): v is Record<L, FieldsType, T>
|
||||
{
|
||||
return (Record.isRecord(v)) && is(ci.label, v.label) && (ci.arity === v.length);
|
||||
}
|
||||
|
||||
export function makeConstructor<Fs, T = GenericEmbedded>()
|
||||
export function makeConstructor<Fs, T extends Embeddable = GenericEmbedded>()
|
||||
: (<L extends Value<T>, Names extends Tuple<keyof Fs>>(label: L, fieldNames: Names) =>
|
||||
RecordConstructor<L, Fs, Names, T>)
|
||||
{
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export * from './annotated';
|
||||
export * from './base64';
|
||||
export * from './bytes';
|
||||
export * from './codec';
|
||||
export * from './compound';
|
||||
|
@ -12,7 +13,9 @@ export * from './float';
|
|||
export * from './fold';
|
||||
export * from './fromjs';
|
||||
export * from './is';
|
||||
export * from './jsdictionary';
|
||||
export * from './merge';
|
||||
export * from './order';
|
||||
export * from './reader';
|
||||
export * from './record';
|
||||
export * from './strip';
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { Value } from "./values";
|
||||
import { Annotated } from "./annotated";
|
||||
import { Record, Tuple } from "./record";
|
||||
import { Set, Dictionary } from "./dictionary";
|
||||
import type { GenericEmbedded } from "./embedded";
|
||||
import { Set, Dictionary, DictionaryMap } from "./dictionary";
|
||||
import type { Embeddable, GenericEmbedded } from "./embedded";
|
||||
|
||||
export function unannotate<T = GenericEmbedded>(v: Value<T>): Value<T> {
|
||||
export function unannotate<T extends Embeddable = GenericEmbedded>(v: Value<T>): Value<T> {
|
||||
return Annotated.isAnnotated<T>(v) ? v.item : v;
|
||||
}
|
||||
|
||||
export function peel<T = GenericEmbedded>(v: Value<T>): Value<T> {
|
||||
export function peel<T extends Embeddable = GenericEmbedded>(v: Value<T>): Value<T> {
|
||||
return strip(v, 1);
|
||||
}
|
||||
|
||||
export function strip<T = GenericEmbedded>(
|
||||
export function strip<T extends Embeddable = GenericEmbedded>(
|
||||
v: Value<T>,
|
||||
depth: number = Infinity): Value<T>
|
||||
{
|
||||
|
@ -34,7 +34,9 @@ export function strip<T = GenericEmbedded>(
|
|||
} else if (Set.isSet<T>(v.item)) {
|
||||
return v.item.map(walk);
|
||||
} else if (Dictionary.isDictionary<T>(v.item)) {
|
||||
return v.item.mapEntries((e) => [walk(e[0]), walk(e[1])]);
|
||||
const result = new DictionaryMap<T>();
|
||||
new DictionaryMap<T>(v.item).forEach((val, key) => result.set(walk(key), walk(val)));
|
||||
return result.simplifiedValue();
|
||||
} else {
|
||||
return v.item;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Embedded, GenericEmbedded } from './embedded';
|
||||
import { Embeddable, GenericEmbedded, isEmbedded } from './embedded';
|
||||
import type { Value } from './values';
|
||||
|
||||
import { Annotated } from './annotated';
|
||||
|
@ -8,22 +8,28 @@ import { Writer, WriterOptions, EmbeddedWriter, WriterState } from './writer';
|
|||
import { fromJS } from './fromjs';
|
||||
import { Reader, ReaderOptions } from './reader';
|
||||
|
||||
export function parse<T = GenericEmbedded>(buffer: string, options?: ReaderOptions<T>): Value<T> {
|
||||
export function parse<T extends Embeddable = GenericEmbedded>(
|
||||
buffer: string,
|
||||
options?: ReaderOptions<T>,
|
||||
): Value<T> {
|
||||
return new Reader<T>(buffer, options).next();
|
||||
}
|
||||
|
||||
export function parseAll<T = GenericEmbedded>(buffer: string, options?: ReaderOptions<T>): Value<T>[] {
|
||||
export function parseAll<T extends Embeddable = GenericEmbedded>(
|
||||
buffer: string,
|
||||
options?: ReaderOptions<T>,
|
||||
): Value<T>[] {
|
||||
return new Reader<T>(buffer, options).readToEnd();
|
||||
}
|
||||
|
||||
export const stringifyEmbeddedWrite: EmbeddedWriter<any> = {
|
||||
export const stringifyEmbeddedWrite = {
|
||||
write(s: WriterState, v: any): void {
|
||||
if (v instanceof GenericEmbedded) {
|
||||
new Writer(s, this).push(v.generic);
|
||||
} else {
|
||||
try {
|
||||
const j = fromJS(v);
|
||||
if (!(j instanceof Embedded)) {
|
||||
if (!isEmbedded(j)) {
|
||||
new Writer(s, this).push(j);
|
||||
return;
|
||||
}
|
||||
|
@ -37,13 +43,13 @@ export const stringifyEmbeddedWrite: EmbeddedWriter<any> = {
|
|||
}
|
||||
};
|
||||
|
||||
export function stringify<T = GenericEmbedded>(x: any, options?: WriterOptions<T>): string {
|
||||
export function stringify<T extends Embeddable = GenericEmbedded>(x: any, options?: WriterOptions<T>): string {
|
||||
options = { ... (options ?? {}) };
|
||||
options.embeddedWrite = options.embeddedWrite ?? stringifyEmbeddedWrite;
|
||||
return Writer.stringify(fromJS<T>(x), options);
|
||||
}
|
||||
|
||||
export function preserves<T>(pieces: TemplateStringsArray, ...values: any[]): string {
|
||||
export function preserves(pieces: TemplateStringsArray, ...values: any[]): string {
|
||||
const result = [pieces[0]];
|
||||
values.forEach((v, i) => {
|
||||
result.push(stringify(v));
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
// Preserves Values.
|
||||
|
||||
import type { Bytes } from './bytes';
|
||||
import type { DoubleFloat, SingleFloat } from './float';
|
||||
import type { DoubleFloat } from './float';
|
||||
import type { Annotated } from './annotated';
|
||||
import type { Set, Dictionary } from './dictionary';
|
||||
import type { Embedded, GenericEmbedded } from './embedded';
|
||||
import type { JsDictionary } from './jsdictionary';
|
||||
import { Set, KeyedDictionary } from './dictionary';
|
||||
import type { Embeddable, GenericEmbedded } from './embedded';
|
||||
|
||||
export type Value<T = GenericEmbedded> =
|
||||
export type Value<T extends Embeddable = GenericEmbedded> =
|
||||
| Atom
|
||||
| Compound<T>
|
||||
| Embedded<T>
|
||||
| T
|
||||
| Annotated<T>;
|
||||
export type Atom =
|
||||
| boolean
|
||||
| SingleFloat
|
||||
| DoubleFloat
|
||||
| number
|
||||
| number | bigint
|
||||
| string
|
||||
| Bytes
|
||||
| symbol;
|
||||
export type Compound<T = GenericEmbedded> =
|
||||
export type Compound<T extends Embeddable = GenericEmbedded> =
|
||||
| (Array<Value<T>> | [Value<T>]) & { label: Value<T> }
|
||||
// ^ expanded from definition of Record<> in record.ts,
|
||||
// because if we use Record<Value<T>, Tuple<Value<T>>, T>,
|
||||
|
@ -28,4 +28,7 @@ export type Compound<T = GenericEmbedded> =
|
|||
// Value<T> to any.
|
||||
| Array<Value<T>>
|
||||
| Set<T>
|
||||
| Dictionary<T>;
|
||||
// v Expanded from definition of Dictionary<> in dictionary.ts,
|
||||
// because of circular-use-of-Value<T> issues.
|
||||
| JsDictionary<Value<T>>
|
||||
| KeyedDictionary<T>;
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
import { isAnnotated } from './is';
|
||||
import { Record, Tuple } from "./record";
|
||||
import type { GenericEmbedded, Embedded, EmbeddedTypeEncode } from "./embedded";
|
||||
import { Embeddable, GenericEmbedded, EmbeddedTypeEncode, isEmbedded } from "./embedded";
|
||||
import { Encoder, EncoderState } from "./encoder";
|
||||
import type { Value } from "./values";
|
||||
import { NUMBER_RE } from './reader';
|
||||
import { encodeBase64 } from './base64';
|
||||
import { DictionaryMap, writeDictionaryOn } from './dictionary';
|
||||
|
||||
export type Writable<T> =
|
||||
export type Writable<T extends Embeddable> =
|
||||
Value<T> | PreserveWritable<T> | Iterable<Value<T>> | ArrayBufferView;
|
||||
|
||||
export interface PreserveWritable<T> {
|
||||
export interface PreserveWritable<T extends Embeddable> {
|
||||
__preserve_text_on__(writer: Writer<T>): void;
|
||||
}
|
||||
|
||||
export function isPreserveWritable<T>(v: any): v is PreserveWritable<T> {
|
||||
export function isPreserveWritable<T extends Embeddable>(v: any): v is PreserveWritable<T> {
|
||||
return typeof v === 'object' && v !== null && '__preserve_text_on__' in v && typeof v.__preserve_text_on__ === 'function';
|
||||
}
|
||||
|
||||
|
@ -190,7 +192,7 @@ export class WriterState {
|
|||
this.pieces.push(buf + '"');
|
||||
}
|
||||
|
||||
couldBeFlat<T>(vs: Writable<T>[]): boolean {
|
||||
couldBeFlat<T extends Embeddable>(vs: Writable<T>[]): boolean {
|
||||
let seenCompound = false;
|
||||
for (let v of vs) {
|
||||
if (Array.isArray(v) || Set.isSet(v) || Map.isMap(v)) {
|
||||
|
@ -205,29 +207,7 @@ export class WriterState {
|
|||
}
|
||||
}
|
||||
|
||||
const BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
|
||||
export function encodeBase64(bs: Uint8Array): string {
|
||||
let s = '';
|
||||
let buffer = 0;
|
||||
let bitcount = 0;
|
||||
for (let b of bs) {
|
||||
buffer = ((buffer & 0x3f) << 8) | b;
|
||||
bitcount += 8;
|
||||
while (bitcount >= 6) {
|
||||
bitcount -= 6;
|
||||
const v = (buffer >> bitcount) & 0x3f;
|
||||
s = s + BASE64[v];
|
||||
}
|
||||
}
|
||||
if (bitcount > 0) {
|
||||
const v = (buffer << (6 - bitcount)) & 0x3f;
|
||||
s = s + BASE64[v];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
export class Writer<T> {
|
||||
export class Writer<T extends Embeddable> {
|
||||
state: WriterState;
|
||||
embeddedWrite: EmbeddedWriter<T>;
|
||||
|
||||
|
@ -246,7 +226,7 @@ export class Writer<T> {
|
|||
}
|
||||
}
|
||||
|
||||
static stringify<T>(v: Writable<T>, options?: WriterOptions<T>): string {
|
||||
static stringify<T extends Embeddable>(v: Writable<T>, options?: WriterOptions<T>): string {
|
||||
const w = new Writer(options);
|
||||
w.push(v);
|
||||
return w.contents();
|
||||
|
@ -278,11 +258,15 @@ export class Writer<T> {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case 'bigint':
|
||||
case 'number':
|
||||
this.state.pieces.push('' + v);
|
||||
break;
|
||||
case 'object':
|
||||
if (isPreserveWritable<unknown>(v)) {
|
||||
if (v === null) {
|
||||
throw new Error("Cannot encode null in Preserves Writer.push");
|
||||
}
|
||||
else if (isPreserveWritable<any>(v)) {
|
||||
v.__preserve_text_on__(this);
|
||||
}
|
||||
else if (isPreserveWritable<T>(v)) {
|
||||
|
@ -315,20 +299,25 @@ export class Writer<T> {
|
|||
else if (isIterable(v)) {
|
||||
this.state.writeSeq('[', ']', v, vv => this.push(vv));
|
||||
}
|
||||
else {
|
||||
((v: Embedded<T>) => {
|
||||
this.state.pieces.push('#!');
|
||||
if ('write' in this.embeddedWrite) {
|
||||
this.embeddedWrite.write(this.state, v.embeddedValue);
|
||||
} else {
|
||||
new Writer(this.state, genericEmbeddedTypeEncode)
|
||||
.push(this.embeddedWrite.toValue(v.embeddedValue));
|
||||
}
|
||||
})(v);
|
||||
else if (isEmbedded(v)) {
|
||||
this.state.pieces.push('#:');
|
||||
if ('write' in this.embeddedWrite) {
|
||||
this.embeddedWrite.write(this.state, v);
|
||||
} else {
|
||||
new Writer(this.state, genericEmbeddedTypeEncode)
|
||||
.push(this.embeddedWrite.toValue(v));
|
||||
}
|
||||
} else {
|
||||
writeDictionaryOn(new DictionaryMap<T>(v),
|
||||
this,
|
||||
(k, w) => w.push(k),
|
||||
(v, w) => w.push(v));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Internal error: unhandled in Preserves Writer.push for ${v}`);
|
||||
((_: never) => {
|
||||
throw new Error(`Internal error: unhandled in Preserves Writer.push for ${v}`);
|
||||
})(v);
|
||||
}
|
||||
return this; // for chaining
|
||||
}
|
||||
|
|
|
@ -82,37 +82,47 @@ describe('immutable byte arrays', () => {
|
|||
});
|
||||
|
||||
describe('base64 decoder', () => {
|
||||
describe('RFC4648 tests', () => {
|
||||
it('10.0', () => expect(decodeBase64("")).is(Bytes.of()));
|
||||
it('10.1', () => expect(decodeBase64("Zg==")).is(Bytes.of(102)));
|
||||
it('10.2', () => expect(decodeBase64("Zm8=")).is(Bytes.of(102, 111)));
|
||||
it('10.3', () => expect(decodeBase64("Zm9v")).is(Bytes.of(102, 111, 111)));
|
||||
it('10.4', () => expect(decodeBase64("Zm9vYg==")).is(Bytes.of(102, 111, 111, 98)));
|
||||
it('10.5', () => expect(decodeBase64("Zm9vYmE=")).is(Bytes.of(102, 111, 111, 98, 97)));
|
||||
it('10.6', () => expect(decodeBase64("Zm9vYmFy")).is(Bytes.of(102, 111, 111, 98, 97, 114)));
|
||||
const d64 = (s: string) => Bytes.from(decodeBase64(s));
|
||||
|
||||
it('10.1b', () => expect(decodeBase64("Zg")).is(Bytes.of(102)));
|
||||
it('10.2b', () => expect(decodeBase64("Zm8")).is(Bytes.of(102, 111)));
|
||||
it('10.4b', () => expect(decodeBase64("Zm9vYg")).is(Bytes.of(102, 111, 111, 98)));
|
||||
it('10.5b', () => expect(decodeBase64("Zm9vYmE")).is(Bytes.of(102, 111, 111, 98, 97)));
|
||||
describe('RFC4648 tests', () => {
|
||||
it('10.0', () => expect(d64("")).is(Bytes.of()));
|
||||
it('10.1', () => expect(d64("Zg==")).is(Bytes.of(102)));
|
||||
it('10.2', () => expect(d64("Zm8=")).is(Bytes.of(102, 111)));
|
||||
it('10.3', () => expect(d64("Zm9v")).is(Bytes.of(102, 111, 111)));
|
||||
it('10.4', () => expect(d64("Zm9vYg==")).is(Bytes.of(102, 111, 111, 98)));
|
||||
it('10.5', () => expect(d64("Zm9vYmE=")).is(Bytes.of(102, 111, 111, 98, 97)));
|
||||
it('10.6', () => expect(d64("Zm9vYmFy")).is(Bytes.of(102, 111, 111, 98, 97, 114)));
|
||||
|
||||
it('10.1b', () => expect(d64("Zg")).is(Bytes.of(102)));
|
||||
it('10.2b', () => expect(d64("Zm8")).is(Bytes.of(102, 111)));
|
||||
it('10.4b', () => expect(d64("Zm9vYg")).is(Bytes.of(102, 111, 111, 98)));
|
||||
it('10.5b', () => expect(d64("Zm9vYmE")).is(Bytes.of(102, 111, 111, 98, 97)));
|
||||
});
|
||||
|
||||
describe('RFC4648 examples', () => {
|
||||
it('example0', () =>
|
||||
expect(decodeBase64('FPucA9l+')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e)));
|
||||
expect(d64('FPucA9l+')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e)));
|
||||
it('example1', () =>
|
||||
expect(decodeBase64('FPucA9k=')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03, 0xd9)));
|
||||
expect(d64('FPucA9k=')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03, 0xd9)));
|
||||
it('example1b', () =>
|
||||
expect(decodeBase64('FPucA9k')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03, 0xd9)));
|
||||
expect(d64('FPucA9k')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03, 0xd9)));
|
||||
it('example2', () =>
|
||||
expect(decodeBase64('FPucAw==')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03)));
|
||||
expect(d64('FPucAw==')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03)));
|
||||
it('example2b', () =>
|
||||
expect(decodeBase64('FPucAw=')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03)));
|
||||
expect(d64('FPucAw=')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03)));
|
||||
it('example2c', () =>
|
||||
expect(decodeBase64('FPucAw')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03)));
|
||||
expect(d64('FPucAw')).is(Bytes.of(0x14, 0xfb, 0x9c, 0x03)));
|
||||
});
|
||||
|
||||
describe('Misc test cases', () => {
|
||||
it('gQ==', () => expect(decodeBase64('gQ==')).is(Bytes.of(0x81)));
|
||||
it('gQ==', () => expect(d64('gQ==')).is(Bytes.of(0x81)));
|
||||
});
|
||||
});
|
||||
|
||||
describe('latin1 quasi-decoder', () => {
|
||||
it('decodes ascii', () => expect(Bytes.fromLatin1('abc')).is(Bytes.of(97, 98, 99)));
|
||||
it('encodes ascii', () => expect(Bytes.of(97, 98, 99).toLatin1()).is('abc'));
|
||||
it('decodes unprintables', () => expect(Bytes.fromLatin1('\x00\x01a\xfe\xff')).is(Bytes.of(0, 1, 97, 254, 255)));
|
||||
it('encodes unprintables', () => expect(Bytes.of(0, 1, 97, 254, 255).toLatin1()).is('\x00\x01a\xfe\xff'));
|
||||
it('rejects out-of-bounds', () => expect(() => Bytes.fromLatin1('ac╔b')).toThrowError('Codepoint out of range'));
|
||||
});
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import {
|
||||
Value,
|
||||
Dictionary,
|
||||
decode, decodeWithAnnotations, encode, encodeWithAnnotations, canonicalEncode,
|
||||
decode, decodeWithAnnotations, encode, canonicalEncode,
|
||||
DecodeError, ShortPacket,
|
||||
Bytes, Record,
|
||||
annotate,
|
||||
strip, peel,
|
||||
preserves,
|
||||
stringify,
|
||||
fromJS,
|
||||
Constants,
|
||||
Encoder,
|
||||
|
@ -15,10 +15,11 @@ import {
|
|||
EmbeddedType,
|
||||
DecoderState,
|
||||
Decoder,
|
||||
Embedded,
|
||||
embed,
|
||||
genericEmbeddedTypeDecode,
|
||||
genericEmbeddedTypeEncode,
|
||||
parse,
|
||||
Embedded,
|
||||
KeyedDictionary,
|
||||
} from '../src/index';
|
||||
const { Tag } = Constants;
|
||||
import './test-utils';
|
||||
|
@ -76,11 +77,11 @@ describe('parsing from subarray', () => {
|
|||
|
||||
describe('reusing buffer space', () => {
|
||||
it('should be done safely, even with nested dictionaries', () => {
|
||||
expect(canonicalEncode(fromJS(['aaa', Dictionary.fromJS({a: 1}), 'zzz'])).toHex()).is(
|
||||
expect(canonicalEncode(fromJS(['aaa', Dictionary.stringMap({a: 1}), 'zzz'])).toHex()).is(
|
||||
`b5
|
||||
b103616161
|
||||
b7
|
||||
b10161 91
|
||||
b10161 b00101
|
||||
84
|
||||
b1037a7a7a
|
||||
84`.replace(/\s+/g, ''));
|
||||
|
@ -88,33 +89,29 @@ describe('reusing buffer space', () => {
|
|||
});
|
||||
|
||||
describe('encoding and decoding embeddeds', () => {
|
||||
class LookasideEmbeddedType implements EmbeddedType<object> {
|
||||
readonly objects: object[];
|
||||
class LookasideEmbeddedType implements EmbeddedType<Embedded<object>> {
|
||||
readonly objects: Embedded<object>[];
|
||||
|
||||
constructor(objects: object[]) {
|
||||
constructor(objects: Embedded<object>[]) {
|
||||
this.objects = objects;
|
||||
}
|
||||
|
||||
decode(d: DecoderState): object {
|
||||
decode(d: DecoderState): Embedded<object> {
|
||||
return this.fromValue(new Decoder<GenericEmbedded>(d).next());
|
||||
}
|
||||
|
||||
encode(e: EncoderState, v: object): void {
|
||||
encode(e: EncoderState, v: Embedded<object>): void {
|
||||
new Encoder(e).push(this.toValue(v));
|
||||
}
|
||||
|
||||
equals(a: object, b: object): boolean {
|
||||
return Object.is(a, b);
|
||||
}
|
||||
|
||||
fromValue(v: Value<GenericEmbedded>): object {
|
||||
fromValue(v: Value<GenericEmbedded>): Embedded<object> {
|
||||
if (typeof v !== 'number' || v < 0 || v >= this.objects.length) {
|
||||
throw new Error("Unknown embedded target");
|
||||
throw new Error(`Unknown embedded target: ${stringify(v)}`);
|
||||
}
|
||||
return this.objects[v];
|
||||
}
|
||||
|
||||
toValue(v: object): number {
|
||||
toValue(v: Embedded<object>): number {
|
||||
let i = this.objects.indexOf(v);
|
||||
if (i !== -1) return i;
|
||||
this.objects.push(v);
|
||||
|
@ -123,8 +120,8 @@ describe('encoding and decoding embeddeds', () => {
|
|||
}
|
||||
|
||||
it('should encode using embeddedId when no function has been supplied', () => {
|
||||
const A1 = embed({a: 1});
|
||||
const A2 = embed({a: 1});
|
||||
const A1 = new Embedded({a: 1});
|
||||
const A2 = new Embedded({a: 1});
|
||||
const bs1 = canonicalEncode(A1);
|
||||
const bs2 = canonicalEncode(A2);
|
||||
const bs3 = canonicalEncode(A1);
|
||||
|
@ -137,51 +134,116 @@ describe('encoding and decoding embeddeds', () => {
|
|||
expect(bs1).is(bs3);
|
||||
});
|
||||
it('should refuse to decode embeddeds when no function has been supplied', () => {
|
||||
expect(() => decode(Bytes.from([Tag.Embedded, Tag.SmallInteger_lo])))
|
||||
expect(() => decode(Bytes.from([Tag.Embedded, Tag.False])))
|
||||
.toThrow("Embeddeds not permitted at this point in Preserves document");
|
||||
});
|
||||
it('should encode properly', () => {
|
||||
const objects: object[] = [];
|
||||
const objects: Embedded<object>[] = [];
|
||||
const pt = new LookasideEmbeddedType(objects);
|
||||
const A = embed({a: 1});
|
||||
const B = embed({b: 2});
|
||||
const A = new Embedded({a: 1});
|
||||
const B = new Embedded({b: 2});
|
||||
expect(encode([A, B], { embeddedEncode: pt })).is(
|
||||
Bytes.from([Tag.Sequence,
|
||||
Tag.Embedded, Tag.SmallInteger_lo,
|
||||
Tag.Embedded, Tag.SmallInteger_lo + 1,
|
||||
Tag.Embedded, Tag.SignedInteger, 0,
|
||||
Tag.Embedded, Tag.SignedInteger, 1, 1,
|
||||
Tag.End]));
|
||||
expect(objects).toEqual([A.embeddedValue, B.embeddedValue]);
|
||||
expect(objects).toEqual([A, B]);
|
||||
});
|
||||
it('should decode properly', () => {
|
||||
const objects: object[] = [];
|
||||
const objects: Embedded<object>[] = [];
|
||||
const pt = new LookasideEmbeddedType(objects);
|
||||
const X: Embedded<object> = embed({x: 123});
|
||||
const Y: Embedded<object> = embed({y: 456});
|
||||
objects.push(X.embeddedValue);
|
||||
objects.push(Y.embeddedValue);
|
||||
const X = new Embedded({x: 123});
|
||||
const Y = new Embedded({y: 456});
|
||||
objects.push(X);
|
||||
objects.push(Y);
|
||||
expect(decode(Bytes.from([
|
||||
Tag.Sequence,
|
||||
Tag.Embedded, Tag.SmallInteger_lo,
|
||||
Tag.Embedded, Tag.SmallInteger_lo + 1,
|
||||
Tag.Embedded, Tag.SignedInteger, 0,
|
||||
Tag.Embedded, Tag.SignedInteger, 1, 1,
|
||||
Tag.End
|
||||
]), { embeddedDecode: pt })).is([X, Y]);
|
||||
});
|
||||
it('should store embeddeds embedded in map keys correctly', () => {
|
||||
const A1a = {a: 1};
|
||||
const A1: Embedded<object> = embed(A1a);
|
||||
const A2: Embedded<object> = embed({a: 1});
|
||||
const m = new Dictionary<object, number>();
|
||||
const A1a = new Embedded({a: 1});
|
||||
const A1 = A1a;
|
||||
const A2 = new Embedded({a: 1});
|
||||
const m = new KeyedDictionary<Embedded<object>, Value<Embedded<object>>, number>();
|
||||
m.set([A1], 1);
|
||||
m.set([A2], 2);
|
||||
expect(m.get(A1)).toBeUndefined();
|
||||
expect(m.get([A1])).toBe(1);
|
||||
expect(m.get([A2])).toBe(2);
|
||||
expect(m.get([embed({a: 1})])).toBeUndefined();
|
||||
A1a.a = 3;
|
||||
expect(m.get([{a: 1}])).toBeUndefined();
|
||||
A1a.value.a = 3;
|
||||
expect(m.get([A1])).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('integer text parsing', () => {
|
||||
it('should work for zero', () => {
|
||||
expect(parse('0')).is(0);
|
||||
});
|
||||
|
||||
it('should work for smallish positive integers', () => {
|
||||
expect(parse('60000')).is(60000);
|
||||
});
|
||||
it('should work for smallish negative integers', () => {
|
||||
expect(parse('-60000')).is(-60000);
|
||||
});
|
||||
|
||||
it('should work for largeish positive integers', () => {
|
||||
expect(parse('1234567812345678123456781234567'))
|
||||
.is(BigInt("1234567812345678123456781234567"));
|
||||
});
|
||||
it('should work for largeish negative integers', () => {
|
||||
expect(parse('-1234567812345678123456781234567'))
|
||||
.is(BigInt("-1234567812345678123456781234567"));
|
||||
});
|
||||
|
||||
it('should work for larger positive integers', () => {
|
||||
expect(parse('12345678123456781234567812345678'))
|
||||
.is(BigInt("12345678123456781234567812345678"));
|
||||
});
|
||||
it('should work for larger negative integers', () => {
|
||||
expect(parse('-12345678123456781234567812345678'))
|
||||
.is(BigInt("-12345678123456781234567812345678"));
|
||||
});
|
||||
});
|
||||
|
||||
describe('integer binary encoding', () => {
|
||||
it('should work for zero integers', () => {
|
||||
expect(encode(0)).is(Bytes.fromHex('b000'));
|
||||
});
|
||||
it('should work for zero bigints', () => {
|
||||
expect(encode(BigInt(0))).is(Bytes.fromHex('b000'));
|
||||
});
|
||||
|
||||
it('should work for smallish positive integers', () => {
|
||||
expect(encode(60000)).is(Bytes.fromHex('b00300ea60'));
|
||||
});
|
||||
it('should work for smallish negative integers', () => {
|
||||
expect(encode(-60000)).is(Bytes.fromHex('b003ff15a0'));
|
||||
});
|
||||
|
||||
it('should work for largeish positive integers', () => {
|
||||
expect(encode(BigInt("1234567812345678123456781234567")))
|
||||
.is(Bytes.fromHex('b00d0f951a8f2b4b049d518b923187'));
|
||||
});
|
||||
it('should work for largeish negative integers', () => {
|
||||
expect(encode(BigInt("-1234567812345678123456781234567")))
|
||||
.is(Bytes.fromHex('b00df06ae570d4b4fb62ae746dce79'));
|
||||
});
|
||||
|
||||
it('should work for larger positive integers', () => {
|
||||
expect(encode(BigInt("12345678123456781234567812345678")))
|
||||
.is(Bytes.fromHex('b00e009bd30997b0ee2e252f73b5ef4e'));
|
||||
});
|
||||
it('should work for larger negative integers', () => {
|
||||
expect(encode(BigInt("-12345678123456781234567812345678")))
|
||||
.is(Bytes.fromHex('b00eff642cf6684f11d1dad08c4a10b2'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('common test suite', () => {
|
||||
const samples_bin = fs.readFileSync(__dirname + '/../../../../../tests/samples.bin');
|
||||
const samples = decodeWithAnnotations(samples_bin, { embeddedDecode: genericEmbeddedTypeDecode });
|
||||
|
@ -191,104 +253,58 @@ describe('common test suite', () => {
|
|||
}>()(Symbol.for('TestCases'), ['cases']);
|
||||
type TestCases = ReturnType<typeof TestCases>;
|
||||
|
||||
function DS(bs: Bytes) {
|
||||
return decode(bs, { embeddedDecode: genericEmbeddedTypeDecode });
|
||||
function encodeBinary(v: Value<GenericEmbedded>): Bytes {
|
||||
return encode(v, { canonical: true, embeddedEncode: genericEmbeddedTypeEncode });
|
||||
}
|
||||
function D(bs: Bytes) {
|
||||
return decodeWithAnnotations(bs, { embeddedDecode: genericEmbeddedTypeDecode });
|
||||
function looseEncodeBinary(v: Value<GenericEmbedded>): Bytes {
|
||||
return encode(v, { canonical: false, includeAnnotations: true, embeddedEncode: genericEmbeddedTypeEncode });
|
||||
}
|
||||
function E(v: Value<GenericEmbedded>) {
|
||||
return encodeWithAnnotations(v, { embeddedEncode: genericEmbeddedTypeEncode });
|
||||
function annotatedBinary(v: Value<GenericEmbedded>): Bytes {
|
||||
return encode(v, { canonical: true, includeAnnotations: true, embeddedEncode: genericEmbeddedTypeEncode });
|
||||
}
|
||||
function decodeBinary(bs: Bytes): Value<GenericEmbedded> {
|
||||
return decode(bs, { includeAnnotations: true, embeddedDecode: genericEmbeddedTypeDecode });
|
||||
}
|
||||
function encodeText(v: Value<GenericEmbedded>): string {
|
||||
return stringify(v, { includeAnnotations: true, embeddedWrite: genericEmbeddedTypeEncode });
|
||||
}
|
||||
function decodeText(s: string): Value<GenericEmbedded> {
|
||||
return parse(s, { includeAnnotations: true, embeddedDecode: genericEmbeddedTypeDecode });
|
||||
}
|
||||
|
||||
interface ExpectedValues {
|
||||
[testName: string]: ({
|
||||
value: Value<GenericEmbedded>;
|
||||
} | {
|
||||
forward: Value<GenericEmbedded>;
|
||||
back: Value<GenericEmbedded>;
|
||||
});
|
||||
}
|
||||
|
||||
const expectedValues: ExpectedValues = {
|
||||
annotation1: { forward: annotate<GenericEmbedded>(9, "abc"),
|
||||
back: 9 },
|
||||
annotation2: { forward: annotate<GenericEmbedded>([[], annotate<GenericEmbedded>([], "x")],
|
||||
"abc",
|
||||
"def"),
|
||||
back: [[], []] },
|
||||
annotation3: { forward: annotate<GenericEmbedded>(5,
|
||||
annotate<GenericEmbedded>(2, 1),
|
||||
annotate<GenericEmbedded>(4, 3)),
|
||||
back: 5 },
|
||||
annotation5: {
|
||||
forward: annotate<GenericEmbedded>(
|
||||
Record<symbol, any>(Symbol.for('R'),
|
||||
[annotate<GenericEmbedded>(Symbol.for('f'),
|
||||
Symbol.for('af'))]),
|
||||
Symbol.for('ar')),
|
||||
back: Record<Value<GenericEmbedded>, any>(Symbol.for('R'), [Symbol.for('f')])
|
||||
},
|
||||
annotation6: {
|
||||
forward: Record<Value<GenericEmbedded>, any>(
|
||||
annotate<GenericEmbedded>(Symbol.for('R'),
|
||||
Symbol.for('ar')),
|
||||
[annotate<GenericEmbedded>(Symbol.for('f'),
|
||||
Symbol.for('af'))]),
|
||||
back: Record<symbol, any>(Symbol.for('R'), [Symbol.for('f')])
|
||||
},
|
||||
annotation7: {
|
||||
forward: annotate<GenericEmbedded>([], Symbol.for('a'), Symbol.for('b'), Symbol.for('c')),
|
||||
back: []
|
||||
},
|
||||
list1: {
|
||||
forward: [1, 2, 3, 4],
|
||||
back: [1, 2, 3, 4]
|
||||
},
|
||||
record2: {
|
||||
value: Observe(Record(Symbol.for("speak"), [
|
||||
Discard(),
|
||||
Capture(Discard())
|
||||
]))
|
||||
},
|
||||
};
|
||||
|
||||
type Variety = 'normal' | 'nondeterministic' | 'decode';
|
||||
type Variety = 'normal' | 'nondeterministic';
|
||||
|
||||
function runTestCase(variety: Variety,
|
||||
tName: string,
|
||||
binaryForm: Bytes,
|
||||
annotatedTextForm: Value<GenericEmbedded>)
|
||||
binary: Bytes,
|
||||
annotatedValue: Value<GenericEmbedded>)
|
||||
{
|
||||
describe(tName, () => {
|
||||
const textForm = strip(annotatedTextForm);
|
||||
const {forward, back} = (function () {
|
||||
const entry = expectedValues[tName] ?? {value: textForm};
|
||||
if ('value' in entry) {
|
||||
return {forward: entry.value, back: entry.value};
|
||||
} else if ('forward' in entry && 'back' in entry) {
|
||||
return entry;
|
||||
} else {
|
||||
throw new Error('Invalid expectedValues entry for ' + tName);
|
||||
}
|
||||
})();
|
||||
it('should match the expected value', () => expect(textForm).is(back));
|
||||
it('should round-trip', () => expect(DS(E(textForm))).is(back));
|
||||
it('should go forward', () => expect(DS(E(forward))).is(back));
|
||||
it('should go back', () => expect(DS(binaryForm)).is(back));
|
||||
it('should go back with annotations',
|
||||
() => expect(D(E(annotatedTextForm))).is(annotatedTextForm));
|
||||
if (variety !== 'decode' && variety !== 'nondeterministic') {
|
||||
it('should encode correctly', () => expect(E(forward)).is(binaryForm));
|
||||
it('should encode correctly with annotations',
|
||||
() => expect(E(annotatedTextForm)).is(binaryForm));
|
||||
const stripped = strip(annotatedValue);
|
||||
it('should round-trip, canonically', () =>
|
||||
expect(decodeBinary(encodeBinary(annotatedValue))).is(stripped));
|
||||
it('should go back, stripped', () =>
|
||||
expect(strip(decodeBinary(binary))).is(stripped));
|
||||
it('should go back', () =>
|
||||
expect(decodeBinary(binary)).is(annotatedValue));
|
||||
it('should round-trip, with annotations', () =>
|
||||
expect(decodeBinary(annotatedBinary(annotatedValue))).is(annotatedValue));
|
||||
it('should round-trip as text, stripped', () =>
|
||||
expect(decodeText(encodeText(stripped))).is(stripped));
|
||||
it('should round-trip as text, with annotations', () =>
|
||||
expect(decodeText(encodeText(annotatedValue))).is(annotatedValue));
|
||||
it('should go forward', () =>
|
||||
expect(annotatedBinary(annotatedValue)).is(binary));
|
||||
if (variety === 'normal') {
|
||||
it('should go forward, loosely', () =>
|
||||
expect(looseEncodeBinary(annotatedValue)).is(binary));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const tests = (peel(TestCases._.cases(peel(samples) as TestCases)) as
|
||||
Dictionary<GenericEmbedded>);
|
||||
tests.forEach((t0: Value<GenericEmbedded>, tName0: Value<GenericEmbedded>) => {
|
||||
Dictionary.asMap(tests).forEach((t0, tName0) => {
|
||||
const tName = Symbol.keyFor(strip(tName0) as symbol)!;
|
||||
const t = peel(t0) as Record<symbol, any, GenericEmbedded>;
|
||||
switch (t.label) {
|
||||
|
@ -298,13 +314,10 @@ describe('common test suite', () => {
|
|||
case Symbol.for('NondeterministicTest'):
|
||||
runTestCase('nondeterministic', tName, strip(t[0]) as Bytes, t[1]);
|
||||
break;
|
||||
case Symbol.for('DecodeTest'):
|
||||
runTestCase('decode', tName, strip(t[0]) as Bytes, t[1]);
|
||||
break;
|
||||
case Symbol.for('DecodeError'):
|
||||
describe(tName, () => {
|
||||
it('should fail with DecodeError', () => {
|
||||
expect(() => D(strip(t[0]) as Bytes))
|
||||
expect(() => decodeBinary(strip(t[0]) as Bytes))
|
||||
.toThrowFilter(e =>
|
||||
DecodeError.isDecodeError(e) &&
|
||||
!ShortPacket.isShortPacket(e));
|
||||
|
@ -315,15 +328,29 @@ describe('common test suite', () => {
|
|||
case Symbol.for('DecodeShort'):
|
||||
describe(tName, () => {
|
||||
it('should fail with ShortPacket', () => {
|
||||
expect(() => D(strip(t[0]) as Bytes))
|
||||
expect(() => decodeBinary(strip(t[0]) as Bytes))
|
||||
.toThrowFilter(e => ShortPacket.isShortPacket(e));
|
||||
});
|
||||
});
|
||||
break;
|
||||
case Symbol.for('ParseError'):
|
||||
describe(tName, () => {
|
||||
it('should fail with DecodeError', () => {
|
||||
expect(() => parse(strip(t[0]) as string))
|
||||
.toThrowFilter(e =>
|
||||
DecodeError.isDecodeError(e) &&
|
||||
!ShortPacket.isShortPacket(e));
|
||||
});
|
||||
});
|
||||
break;
|
||||
case Symbol.for('ParseEOF'):
|
||||
case Symbol.for('ParseShort'):
|
||||
/* Skipped for now, until we have an implementation of text syntax */
|
||||
describe(tName, () => {
|
||||
it('should fail with ShortPacket', () => {
|
||||
expect(() => parse(strip(t[0]) as string))
|
||||
.toThrowFilter(e => ShortPacket.isShortPacket(e));
|
||||
});
|
||||
});
|
||||
break;
|
||||
default:{
|
||||
const e = new Error(preserves`Unsupported test kind ${t}`);
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import { Pexpr, Value, fromJS, parse, stringify } from '../src/index';
|
||||
import './test-utils';
|
||||
|
||||
function P(s: string): Value<any>[] {
|
||||
return parse(s, { includeAnnotations: true });
|
||||
}
|
||||
|
||||
describe('basics', () => {
|
||||
it('simple example', () => {
|
||||
const r = new Pexpr.Reader('#!foo\n<bar {zot ::quux}, [a; b; c;]>');
|
||||
const d = r.nextDocument();
|
||||
expect(fromJS(d)).is(P(`[\n#!foo\n
|
||||
<r bar <b zot <p |::|> quux> <p |,|> [a <p |;|> b <p |;|> c <p |;|>]>]`));
|
||||
});
|
||||
|
||||
it('simple group', () => {
|
||||
const r = new Pexpr.Reader('(+ 1 2)');
|
||||
expect(fromJS(r.nextDocument())).is(P('[<g + 1 2>]'));
|
||||
});
|
||||
|
||||
it('asPreserves', () => {
|
||||
const s = '{a: b, c: d, e: [1, 2 <r 3 4>]}';
|
||||
const d = new Pexpr.Reader(s).nextDocument();
|
||||
const v = Pexpr.asPreserves(d.get(0)!);
|
||||
expect(v).is(P(s));
|
||||
});
|
||||
});
|
||||
|
||||
describe('trailing comments', () => {
|
||||
it('basics 1', () => {
|
||||
const d = new Pexpr.Reader('# a comment with nothing after').nextDocument();
|
||||
expect(d.annotations?.[d.exprs.length].get(0)?.item).toBe('a comment with nothing after');
|
||||
});
|
||||
|
||||
it('basics 2', () => {
|
||||
const d = new Pexpr.Reader('# a comment with nothing after\n').nextDocument();
|
||||
expect(d.annotations?.[d.exprs.length].get(0)?.item).toBe('a comment with nothing after');
|
||||
});
|
||||
|
||||
it('inside a sequence', () => {
|
||||
const d = new Pexpr.Reader('[\n1\n# a comment with nothing after\n]\n').nextDocument();
|
||||
const seq = d.get(0)?.item as Pexpr.Compound;
|
||||
expect(seq.annotations?.[seq.exprs.length].get(0)?.item).toBe('a comment with nothing after');
|
||||
});
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import { Bytes, Decoder, genericEmbeddedType, encode, Reader, Double } from '../src/index';
|
||||
import { Bytes, Decoder, genericEmbeddedType, encode, Reader, Double, KeyedDictionary, Value } from '../src/index';
|
||||
import './test-utils';
|
||||
|
||||
import * as fs from 'fs';
|
||||
|
@ -36,4 +36,23 @@ describe('reading common test suite', () => {
|
|||
expect(new Reader('123.0').next()).toEqual(Double(123.0));
|
||||
expect(new Reader('123.00').next()).toEqual(Double(123.0));
|
||||
});
|
||||
|
||||
it('should produce a sensible JS object for symbol-keyed dictionaries', () => {
|
||||
expect(new Reader('{a: 1, b: 2}').next()).toEqual({a: 1, b: 2});
|
||||
});
|
||||
|
||||
it('should produce a sensible dictionary for mixed-keyed dictionaries', () => {
|
||||
expect(new Reader('{a: 1, "b": 2}').next()).is(
|
||||
new KeyedDictionary([[Symbol.for('a'), 1], ["b", 2]] as [Value, Value][]));
|
||||
});
|
||||
|
||||
it('should produce a sensible dictionary for string-keyed dictionaries', () => {
|
||||
expect(new Reader('{"a": 1, "b": 2}').next()).is(
|
||||
new KeyedDictionary([["a", 1], ["b", 2]] as [Value, Value][]));
|
||||
});
|
||||
|
||||
it('should produce a sensible dictionary for integer-keyed dictionaries', () => {
|
||||
expect(new Reader('{9: 1, 8: 2}').next()).is(
|
||||
new KeyedDictionary([[9, 1], [8, 2]] as [Value, Value][]));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Value, is, preserves } from '../src/index';
|
||||
import { Value, is, preserves, Embeddable } from '../src/index';
|
||||
import '../src/node_support';
|
||||
|
||||
declare global {
|
||||
namespace jest {
|
||||
interface Matchers<R> {
|
||||
is<T>(expected: Value<T>): R;
|
||||
is<T extends Embeddable>(expected: Value<T>): R;
|
||||
toThrowFilter(f: (e: Error) => boolean): R;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
import { Single, Double, fromJS, Dictionary, IDENTITY_FOLD, fold, mapEmbeddeds, Value, embed } from '../src/index';
|
||||
import { Double, fromJS, IDENTITY_FOLD, fold, mapEmbeddeds, Value, preserves, KeyedDictionary, Embeddable, Embedded } from '../src/index';
|
||||
import './test-utils';
|
||||
|
||||
describe('Single', () => {
|
||||
it('should print reasonably', () => {
|
||||
expect(Single(123.45).toString()).toEqual("123.45f");
|
||||
});
|
||||
});
|
||||
|
||||
describe('Double', () => {
|
||||
it('should print reasonably', () => {
|
||||
expect(Double(123.45).toString()).toEqual("123.45");
|
||||
|
@ -14,26 +8,26 @@ describe('Double', () => {
|
|||
});
|
||||
|
||||
describe('fold', () => {
|
||||
function mkv<T extends object>(t: T): Value<T> {
|
||||
function mkv<T extends Embeddable>(t: T): Value<T> {
|
||||
return fromJS<T>([
|
||||
1,
|
||||
2,
|
||||
new Dictionary([[[3, 4], fromJS([5, 6])],
|
||||
['a', 1],
|
||||
['b', true]]),
|
||||
Single(3.4),
|
||||
new KeyedDictionary<T>([[[3, 4], fromJS([5, 6])],
|
||||
['a', 1],
|
||||
['b', true]]),
|
||||
Double(3.4),
|
||||
t,
|
||||
]);
|
||||
}
|
||||
|
||||
it('should support identity', () => {
|
||||
const w = new Date();
|
||||
const v = mkv(w);
|
||||
const v = mkv(new Embedded(w));
|
||||
expect(fold(v, IDENTITY_FOLD)).is(v);
|
||||
const w1 = new Date();
|
||||
const v1 = mkv(w1);
|
||||
const v1 = mkv(new Embedded(w1));
|
||||
expect(fold(v, IDENTITY_FOLD)).not.is(v1);
|
||||
expect(mapEmbeddeds(v, _t => embed(w1))).is(v1);
|
||||
expect(mapEmbeddeds(v, _t => new Embedded(w1))).is(v1);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -41,4 +35,71 @@ describe('fromJS', () => {
|
|||
it('should map integers to themselves', () => {
|
||||
expect(fromJS(1)).toBe(1);
|
||||
});
|
||||
|
||||
it('should map bigints to themselves', () => {
|
||||
expect(fromJS(BigInt("12345678123456781234567812345678")))
|
||||
.toBe(BigInt("12345678123456781234567812345678"));;
|
||||
});
|
||||
});
|
||||
|
||||
describe('is()', () => {
|
||||
it('should compare small integers sensibly', () => {
|
||||
expect(3).is(3);
|
||||
expect(3).not.is(4);
|
||||
});
|
||||
it('should compare large integers sensibly', () => {
|
||||
const a = BigInt("12345678123456781234567812345678");
|
||||
const b = BigInt("12345678123456781234567812345679");
|
||||
expect(a).is(a);
|
||||
expect(a).is(BigInt("12345678123456781234567812345678"));
|
||||
expect(a).not.is(b);
|
||||
});
|
||||
it('should compare mixed integers sensibly', () => {
|
||||
const a = BigInt("12345678123456781234567812345678");
|
||||
const b = BigInt("3");
|
||||
const c = BigInt("4");
|
||||
expect(3).not.is(a);
|
||||
expect(a).not.is(3);
|
||||
expect(3).not.toBe(b);
|
||||
expect(3).is(b);
|
||||
expect(b).not.toBe(3);
|
||||
expect(b).is(3);
|
||||
expect(3).not.toBe(c);
|
||||
expect(3).not.is(c);
|
||||
expect(c).not.toBe(3);
|
||||
expect(c).not.is(3);
|
||||
});
|
||||
it('should compare equivalent JsDictionary and KeyedDictionary values sensibly', () => {
|
||||
const a = {a: 1, b: 2};
|
||||
const b = new KeyedDictionary(
|
||||
[[Symbol.for('a'), 1], [Symbol.for('b'), 2]] as [Value, Value][]);
|
||||
expect(a).is(b);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`preserves` formatter', () => {
|
||||
it('should format numbers', () => {
|
||||
expect(preserves`>${3}<`).toBe('>3<');
|
||||
});
|
||||
it('should format small bigints', () => {
|
||||
expect(preserves`>${BigInt("3")}<`).toBe('>3<');
|
||||
});
|
||||
it('should format big bigints', () => {
|
||||
expect(preserves`>${BigInt("12345678123456781234567812345678")}<`)
|
||||
.toBe('>12345678123456781234567812345678<');
|
||||
});
|
||||
it('should format regular JS objects', () => {
|
||||
expect(preserves`>${({a: 1, b: 2})}<`)
|
||||
.toBe('>{a: 1 b: 2}<');
|
||||
});
|
||||
it('should format dictionaries with string keys', () => {
|
||||
const v = new KeyedDictionary([["a", 1], ["b", 2]]);
|
||||
expect(preserves`>${v}<`)
|
||||
.toBe('>{"a": 1 "b": 2}<');
|
||||
});
|
||||
it('should format dictionaries with symbol keys', () => {
|
||||
const v = new KeyedDictionary([[Symbol.for("a"), 1], [Symbol.for("b"), 2]]);
|
||||
expect(preserves`>${v}<`)
|
||||
.toBe('>{a: 1 b: 2}<');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
dist/
|
||||
lib/
|
|
@ -0,0 +1 @@
|
|||
version-tag-prefix javascript-@preserves/schema-cli@
|
|
@ -0,0 +1 @@
|
|||
# Preserves Schema for TypeScript/JavaScript: Command-line tools
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env node
|
||||
require('../lib/bin/preserves-schema-ts.js').main(process.argv.slice(2));
|
|
@ -0,0 +1,2 @@
|
|||
#!/usr/bin/env node
|
||||
require('../lib/bin/preserves-schemac.js').main(process.argv.slice(2));
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "@preserves/schema-cli",
|
||||
"version": "0.995.206",
|
||||
"description": "Command-line tools for Preserves Schema",
|
||||
"homepage": "https://gitlab.com/preserves/preserves",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": "gitlab:preserves/preserves",
|
||||
"author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>",
|
||||
"scripts": {
|
||||
"clean": "rm -rf lib dist",
|
||||
"prepare": "yarn compile",
|
||||
"compile": "tsc",
|
||||
"compile:watch": "yarn compile -w",
|
||||
"test": "true",
|
||||
"veryclean": "yarn run clean && rm -rf node_modules"
|
||||
},
|
||||
"bin": {
|
||||
"preserves-schema-ts": "./bin/preserves-schema-ts.js",
|
||||
"preserves-schemac": "./bin/preserves-schemac.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/glob": "^7.1",
|
||||
"@types/minimatch": "^3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@preserves/core": "^0.995.206",
|
||||
"@preserves/schema": "^0.995.206",
|
||||
"chalk": "^4.1",
|
||||
"chokidar": "^3.5",
|
||||
"commander": "^7.2",
|
||||
"glob": "^7.1",
|
||||
"minimatch": "^3.0"
|
||||
}
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { glob } from 'glob';
|
||||
import { IdentitySet, formatPosition, Position } from '@preserves/core';
|
||||
import { readSchema } from '../reader';
|
||||
import chalk from 'chalk';
|
||||
import * as M from '../meta';
|
||||
import { IdentitySet, formatPosition, Position } from '@preserves/core';
|
||||
import { readSchema, Meta as M } from '@preserves/schema';
|
||||
|
||||
export interface Diagnostic {
|
||||
type: 'warn' | 'error';
|
|
@ -1,9 +1,8 @@
|
|||
import { compile } from '../index';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import minimatch from 'minimatch';
|
||||
import { Command } from 'commander';
|
||||
import * as M from '../meta';
|
||||
import { compile, Meta as M } from '@preserves/schema';
|
||||
import chalk from 'chalk';
|
||||
import { is, Position } from '@preserves/core';
|
||||
import chokidar from 'chokidar';
|
|
@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|||
import { canonicalEncode, KeyedDictionary, underlying } from '@preserves/core';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import * as M from '../meta';
|
||||
import { Meta as M } from '@preserves/schema';
|
||||
import { expandInputGlob, formatFailures } from './cli-utils';
|
||||
|
||||
export type CommandLineArguments = {
|
||||
|
@ -24,10 +24,9 @@ export function run(options: CommandLineArguments): void {
|
|||
|
||||
if (failures.length === 0) {
|
||||
if (options.bundle) {
|
||||
fs.writeSync(1, underlying(canonicalEncode(M.fromBundle({
|
||||
modules: new KeyedDictionary<M.ModulePath, M.Schema, M.InputEmbedded>(
|
||||
inputFiles.map(i => [i.modulePath, i.schema])),
|
||||
}))));
|
||||
fs.writeSync(1, underlying(canonicalEncode(M.fromBundle(M.Bundle(
|
||||
new KeyedDictionary<M.InputEmbedded, M.ModulePath, M.Schema>(
|
||||
inputFiles.map(i => [i.modulePath, i.schema])))))));
|
||||
} else {
|
||||
fs.writeSync(1, underlying(canonicalEncode(M.fromSchema(inputFiles[0].schema))));
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["es2019", "DOM"],
|
||||
"declaration": true,
|
||||
"baseUrl": "./src",
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"declarationDir": "./lib",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
|
@ -2,3 +2,7 @@
|
|||
|
||||
This is an implementation of [Preserves Schema](https://preserves.dev/preserves-schema.html)
|
||||
for TypeScript and JavaScript.
|
||||
|
||||
This package implements a Schema runtime and a Schema-to-TypeScript compiler, but offers no
|
||||
command line interfaces. See `@preserves/schema-cli` for command-line tools for working with
|
||||
Schema and compiling from Schema to TypeScript.
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
require('../dist/bin/preserves-schema-ts.js').main(process.argv.slice(2));
|
|
@ -1,2 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
require('../dist/bin/preserves-schemac.js').main(process.argv.slice(2));
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@preserves/schema",
|
||||
"version": "0.22.4",
|
||||
"version": "0.995.206",
|
||||
"description": "Schema support for Preserves data serialization format",
|
||||
"homepage": "https://gitlab.com/preserves/preserves",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -13,30 +13,23 @@
|
|||
"types": "lib/index.d.ts",
|
||||
"author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>",
|
||||
"scripts": {
|
||||
"regenerate": "rm -rf ./src/gen && yarn copy-schema && ./bin/preserves-schema-ts.js --output ./src/gen ./dist:schema.prs",
|
||||
"regenerate": "rm -rf ./src/gen && yarn copy-schema && ../schema-cli/bin/preserves-schema-ts.js --output ./src/gen ./dist:*.prs",
|
||||
"clean": "rm -rf lib dist",
|
||||
"prepare": "yarn compile && yarn rollup && yarn copy-schema",
|
||||
"prepare": "yarn compile && yarn rollup && yarn copy-schema && cp preserves-schema-browser.js dist/",
|
||||
"compile": "tsc",
|
||||
"compile:watch": "yarn compile -w",
|
||||
"rollup": "rollup -c",
|
||||
"rollup:watch": "yarn rollup -w",
|
||||
"copy-schema": "mkdir -p ./dist && cp -a ../../../../schema/schema.prs ./dist",
|
||||
"copy-schema": "mkdir -p ./dist && cp -a ../../../../schema/schema.prs ../../../../schema/host.prs ./dist",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"veryclean": "yarn run clean && rm -rf node_modules"
|
||||
},
|
||||
"bin": {
|
||||
"preserves-schema-ts": "./bin/preserves-schema-ts.js",
|
||||
"preserves-schemac": "./bin/preserves-schemac.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@preserves/core": "^0.22.1",
|
||||
"@types/glob": "^7.1",
|
||||
"@types/minimatch": "^3.0",
|
||||
"chalk": "^4.1",
|
||||
"chokidar": "^3.5",
|
||||
"commander": "^7.2",
|
||||
"glob": "^7.1",
|
||||
"minimatch": "^3.0"
|
||||
"@preserves/core": "^0.995.206"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-beautify": "1.14",
|
||||
"js-beautify": "1.15"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
(() => {
|
||||
const I = new PreservesSchema.SchemaInterpreter();
|
||||
|
||||
globalThis.Schema = { __interpreter: I };
|
||||
let schemaReady;
|
||||
globalThis.SchemaReady = new Promise(res => schemaReady = res);
|
||||
|
||||
async function translateScripts() {
|
||||
|
||||
const schemaScripts =
|
||||
Array.from(document.getElementsByTagName('script'))
|
||||
.filter(s => (s.type === 'text/preserves+schema' ||
|
||||
s.type === 'schema'));
|
||||
|
||||
for (const script of schemaScripts) {
|
||||
function complain(message, detail) {
|
||||
const e = new Error(message);
|
||||
e.script = script;
|
||||
e.detail = detail;
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
let sourceCodeBlob;
|
||||
const sourceUrl = script.src || script.getAttribute('data-src') || false;
|
||||
if (sourceUrl) {
|
||||
const res = await fetch(sourceUrl);
|
||||
if (res.ok) {
|
||||
sourceCodeBlob = new Uint8Array(await res.arrayBuffer());
|
||||
} else {
|
||||
complain(`Failed to retrieve schema from ${sourceUrl}`, { res });
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
sourceCodeBlob = new TextEncoder().encode(script.innerHTML);
|
||||
}
|
||||
|
||||
const schemaName = () => {
|
||||
const n = script.getAttribute('name');
|
||||
if (n === null) complain(`<script type="schema"> must have name attribute`);
|
||||
return n;
|
||||
};
|
||||
|
||||
if (sourceCodeBlob[0] >= 128) {
|
||||
// Binary Preserves blob
|
||||
const value = Preserves.decode(sourceCodeBlob);
|
||||
const bundle = PreservesSchema.Meta.toBundle(value);
|
||||
if (bundle !== void 0) {
|
||||
const prefixStr = script.getAttribute('prefix');
|
||||
const bundlePrefix = (prefixStr ? prefixStr.split('.') : []).map(Symbol.for);
|
||||
bundle.modules.forEach((schema, path) => {
|
||||
const modulePath = [... bundlePrefix, ... path];
|
||||
I.env.set(modulePath, schema);
|
||||
});
|
||||
} else {
|
||||
const schema = PreservesSchema.Meta.toSchema(value);
|
||||
if (schema !== void 0) {
|
||||
const modulePath = schemaName().split('.').map(Symbol.for);
|
||||
I.env.set(modulePath, schema);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Presumably text
|
||||
const sourceCode = new TextDecoder('utf-8', { fatal: true }).decode(sourceCodeBlob);
|
||||
const name = schemaName();
|
||||
const schema = PreservesSchema.readSchema(sourceCode, { name });
|
||||
const modulePath = name.split('.').map(Symbol.for);
|
||||
I.env.set(modulePath, schema);
|
||||
}
|
||||
}
|
||||
|
||||
I.moduleTree(Schema);
|
||||
schemaReady();
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', translateScripts);
|
||||
})();
|
|
@ -31,13 +31,6 @@ function cli(name) {
|
|||
output: [{file: `dist/bin/${name}.js`, format: 'commonjs'}],
|
||||
external: [
|
||||
'@preserves/core',
|
||||
'chalk',
|
||||
'chokidar',
|
||||
'fs',
|
||||
'glob',
|
||||
'minimatch',
|
||||
'path',
|
||||
'commander',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
@ -53,6 +46,4 @@ export default [
|
|||
],
|
||||
external: ['@preserves/core'],
|
||||
},
|
||||
cli('preserves-schema-ts'),
|
||||
cli('preserves-schemac'),
|
||||
];
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { stringify } from '@preserves/core';
|
||||
import { JsDictionary, stringify } from '@preserves/core';
|
||||
import * as M from './meta';
|
||||
|
||||
export function checkSchema(schema: M.Schema): (
|
||||
{ ok: true, schema: M.Schema } | { ok: false, problems: Array<string> })
|
||||
{
|
||||
const checker = new Checker();
|
||||
schema.definitions.forEach(checker.checkDefinition.bind(checker));
|
||||
JsDictionary.forEach(schema.definitions, checker.checkDefinition.bind(checker));
|
||||
if (checker.problems.length > 0) {
|
||||
return { ok: false, problems: checker.problems };
|
||||
} else {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { encode, stringify } from "@preserves/core";
|
||||
import { encode, JsDictionary, stringify } from "@preserves/core";
|
||||
import * as M from "./meta";
|
||||
import { CompilerOptions, ModuleContext } from "./compiler/context";
|
||||
import { Formatter, block, seq, braces, opseq } from "./compiler/block";
|
||||
import { Formatter, block, seq, braces } from "./compiler/block";
|
||||
import { typeForDefinition } from "./compiler/gentype";
|
||||
import { converterForDefinition } from "./compiler/genconverter";
|
||||
import { renderType } from "./compiler/rendertype";
|
||||
import { renderTypeWithConversionMixins } from "./compiler/rendertype";
|
||||
import { genConstructor } from "./compiler/genctor";
|
||||
import { unconverterForDefinition } from "./compiler/genunconverter";
|
||||
import { sourceCodeFor } from "./compiler/value";
|
||||
|
@ -29,13 +29,13 @@ export function compile(
|
|||
mod.defineType(seq(`export type _embedded = `, mod.embeddedType, `;`));
|
||||
}
|
||||
|
||||
for (const [name, def] of schema.definitions) {
|
||||
for (const [name, def] of JsDictionary.entries(schema.definitions)) {
|
||||
const t = typeForDefinition(mod.resolver(), def);
|
||||
const nameStr = stringify(name);
|
||||
const resultTypeItem = mod.withAsPreserveMixinType(nameStr, t);
|
||||
const resultTypeItem = seq(nameStr, mod.genericArgsFor(t));
|
||||
|
||||
mod.defineType(seq(`export type ${nameStr}`, mod.genericParametersFor(t),
|
||||
` = `, renderType(mod, t), `;`));
|
||||
` = `, renderTypeWithConversionMixins(mod, t), `;`));
|
||||
|
||||
if (t.kind === 'union') {
|
||||
mod.defineFunctions(nameStr, _ctx =>
|
||||
|
@ -49,11 +49,11 @@ export function compile(
|
|||
}
|
||||
}
|
||||
|
||||
for (const [name0, def] of schema.definitions) {
|
||||
for (const [name0, def] of JsDictionary.entries(schema.definitions)) {
|
||||
const t = typeForDefinition(mod.resolver(), def);
|
||||
const name = name0 as symbol;
|
||||
const nameStr = name0.description!;
|
||||
const resultTypeItem = mod.withAsPreserveMixinType(nameStr, t);
|
||||
const resultTypeItem = seq(nameStr, mod.genericArgsFor(t));
|
||||
|
||||
mod.defineFunctions(nameStr, ctx =>
|
||||
[seq(`export function as${name.description!}`, mod.genericParameters(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Dictionary, KeyedSet, FlexSet, Position, stringify } from "@preserves/core";
|
||||
import { KeyedSet, FlexSet, Position, stringify, DictionaryMap, JsDictionary } from "@preserves/core";
|
||||
import { refPosition } from "../reader";
|
||||
import * as M from "../meta";
|
||||
import { anglebrackets, block, braces, commas, formatItems, Item, keyvalue, seq, opseq } from "./block";
|
||||
import { anglebrackets, block, braces, commas, formatItems, Item, keyvalue, seq } from "./block";
|
||||
import { ANY_TYPE, RefType, Type } from "./type";
|
||||
import { renderType, variantInitFor } from "./rendertype";
|
||||
import { typeForDefinition } from "./gentype";
|
||||
|
@ -27,11 +27,11 @@ export class ModuleContext {
|
|||
readonly options: CompilerOptions;
|
||||
readonly embeddedType: Item;
|
||||
|
||||
readonly literals = new Dictionary<M.InputEmbedded, string>();
|
||||
readonly literals = new DictionaryMap<M.InputEmbedded, string>();
|
||||
readonly preamble: Item[] = [];
|
||||
readonly typedefs: Item[] = [];
|
||||
readonly functiondefs: Item[] = [];
|
||||
readonly imports = new KeyedSet<[M.ModulePath, string, string, string]>();
|
||||
readonly imports = new KeyedSet<M.InputEmbedded, [M.ModulePath, string, string, string]>();
|
||||
|
||||
constructor(
|
||||
env: M.Environment,
|
||||
|
@ -97,7 +97,7 @@ export class ModuleContext {
|
|||
return (ref) => this.lookup(
|
||||
ref,
|
||||
(_p, _t) => Type.ref(ref.name.description!, ref),
|
||||
(modPath, modId, modFile, modExpr, _p, t) => {
|
||||
(modPath, modId, modFile, modExpr, _p, _t) => {
|
||||
this.imports.add([modPath, modId, modFile, modExpr]);
|
||||
return Type.ref(`${modId}${modExpr}.${ref.name.description!}`, ref);
|
||||
},
|
||||
|
@ -137,7 +137,7 @@ export class ModuleContext {
|
|||
null,
|
||||
null);
|
||||
} else {
|
||||
const p = e.schema.definitions.get(name.name);
|
||||
const p = JsDictionary.get(e.schema.definitions, name.name);
|
||||
if (p !== void 0) {
|
||||
let t = () => typeForDefinition(this.resolver(soughtModule), p);
|
||||
if (name.module.length) {
|
||||
|
@ -158,7 +158,7 @@ export class ModuleContext {
|
|||
}
|
||||
|
||||
genericParameters(): Item {
|
||||
return anglebrackets(seq('_embedded = ', this.embeddedType));
|
||||
return anglebrackets(seq('_embedded extends _.Embeddable = ', this.embeddedType));
|
||||
}
|
||||
|
||||
genericParametersFor(t: Type): Item {
|
||||
|
@ -209,19 +209,6 @@ export class ModuleContext {
|
|||
|
||||
return walk(t);
|
||||
}
|
||||
|
||||
withAsPreserveMixinType(name: string, t: Type): Item {
|
||||
if (t.kind === 'unit' || t.kind === 'record' || t.kind === 'union') {
|
||||
return opseq('any', ' & ',
|
||||
seq(name, this.genericArgsFor(t)),
|
||||
braces(seq('__as_preserve__',
|
||||
this.hasEmbedded(t) ? '' : this.genericParameters(),
|
||||
'()',
|
||||
': _.Value', this.genericArgs())));
|
||||
} else {
|
||||
return seq(name, this.genericArgsFor(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class FunctionContext {
|
||||
|
@ -285,16 +272,25 @@ export class FunctionContext {
|
|||
}
|
||||
|
||||
buildCapturedCompound(dest: string): Item {
|
||||
const fields = [
|
||||
... variantInitFor(this.variantName),
|
||||
... this.captures.map(({ fieldName, sourceExpr }) =>
|
||||
keyvalue(fieldName, sourceExpr)),
|
||||
seq(`__as_preserve__() `, block(`return from${this.definitionName}(this)`))
|
||||
];
|
||||
return seq(`${dest} = `, braces(... fields));
|
||||
return seq(`${dest} = `, buildProduct(
|
||||
this.definitionName, this.variantName, this.captures));
|
||||
}
|
||||
}
|
||||
|
||||
export function buildProduct(
|
||||
definitionName: string,
|
||||
variant: string | undefined,
|
||||
initializers: Capture[],
|
||||
): Item {
|
||||
return braces(
|
||||
... variantInitFor(variant),
|
||||
... initializers.map(({ fieldName, sourceExpr }) => keyvalue(fieldName, sourceExpr)),
|
||||
seq(`__as_preserve__() `, block(`return from${M.jsId(definitionName)}(this)`)),
|
||||
seq(`__preserve_on__(e) { e.push(from${M.jsId(definitionName)}(this)); }`),
|
||||
seq(`__preserve_text_on__(w) { w.push(from${M.jsId(definitionName)}(this)); }`),
|
||||
);
|
||||
}
|
||||
|
||||
export class WalkState {
|
||||
modulePath: M.ModulePath;
|
||||
readonly seen: FlexSet<M.Ref>;
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { FunctionContext } from "./context";
|
||||
import * as M from '../meta';
|
||||
import { Item, seq } from "./block";
|
||||
import { Item, seq, parens, anglebrackets } from "./block";
|
||||
import { simpleType, typeFor } from "./gentype";
|
||||
import { ANY_TYPE, Type } from "./type";
|
||||
import { ANY_TYPE, isSymbolType, Type } from "./type";
|
||||
import { renderType } from "./rendertype";
|
||||
|
||||
export function converterForDefinition(
|
||||
ctx: FunctionContext,
|
||||
|
@ -86,7 +87,7 @@ function converterForTuple(ctx: FunctionContext,
|
|||
}
|
||||
|
||||
const lengthCheck = variablePattern === void 0
|
||||
? seq(` && ${src}.length === ${ps.length}`)
|
||||
? seq(` && ${src}.length >= ${ps.length}`)
|
||||
: ((ps.length === 0) ? '' : seq(` && ${src}.length >= ${ps.length}`));
|
||||
|
||||
return knownArray
|
||||
|
@ -94,6 +95,31 @@ function converterForTuple(ctx: FunctionContext,
|
|||
: [seq(`if (_.isSequence(${src})`, lengthCheck, `) `, ctx.block(() => loop(0)))];
|
||||
}
|
||||
|
||||
function encoderForSimplePattern(
|
||||
ctx: FunctionContext,
|
||||
p: M.SimplePattern,
|
||||
): Item | null {
|
||||
switch (p._variant) {
|
||||
case 'Ref':
|
||||
return ctx.mod.lookup(
|
||||
p.value,
|
||||
(_p, t) => `from${M.jsId(p.value.name.description!)}${ctx.mod.genericArgsFor(t())}`,
|
||||
(modPath, modId, modFile, modExpr, _p, t) => {
|
||||
ctx.mod.imports.add([modPath, modId, modFile, modExpr]);
|
||||
return `${modId}${modExpr}.from${M.jsId(p.value.name.description!)}${t ? ctx.mod.genericArgsFor(t()) : ''}`;
|
||||
});
|
||||
case 'embedded':
|
||||
return `_.embed`;
|
||||
case 'seqof': {
|
||||
const e = encoderForSimplePattern(ctx, p.pattern);
|
||||
if (e === null) return null;
|
||||
return seq(`vs => vs.map`, parens(e));
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function converterFor(
|
||||
ctx: FunctionContext,
|
||||
np: M.NamedPattern,
|
||||
|
@ -128,7 +154,6 @@ export function converterForSimple(
|
|||
let valexp: Item = `${src}`;
|
||||
switch (p.atomKind._variant) {
|
||||
case 'Boolean': test = `typeof ${src} === 'boolean'`; break;
|
||||
case 'Float': test = `_.Float.isSingle(${src})`; valexp = `${src}.value`; break;
|
||||
case 'Double': test =`_.Float.isDouble(${src})`; valexp = `${src}.value`; break;
|
||||
case 'SignedInteger': test = `typeof ${src} === 'number'`; break;
|
||||
case 'String': test = `typeof ${src} === 'string'`; break;
|
||||
|
@ -138,7 +163,7 @@ export function converterForSimple(
|
|||
return [seq(`${dest} = `, test, ` ? `, valexp, ` : void 0`)];
|
||||
}
|
||||
case 'embedded':
|
||||
return [`${dest} = _.isEmbedded<_embedded>(${src}) ? ${src}.embeddedValue : void 0`];
|
||||
return [`${dest} = _.isEmbedded<_embedded>(${src}) ? ${src} : void 0`];
|
||||
case 'lit':
|
||||
return [`${dest} = _.is(${src}, ${ctx.mod.literal(p.value)}) ? {} : void 0`];
|
||||
|
||||
|
@ -163,9 +188,12 @@ export function converterForSimple(
|
|||
case 'setof':
|
||||
return [`${dest} = void 0`,
|
||||
seq(`if (_.Set.isSet<_embedded>(${src})) `, ctx.block(() => {
|
||||
const vt = simpleType(ctx.mod.resolver(), p.pattern);
|
||||
const v = ctx.gentempname();
|
||||
return [
|
||||
seq(`${dest} = new _.KeyedSet()`),
|
||||
seq(`${dest} = new _.EncodableSet`,
|
||||
anglebrackets('_embedded', renderType(ctx.mod, vt)),
|
||||
parens(encoderForSimplePattern(ctx, p.pattern) ?? `v => v`)),
|
||||
seq(`for (const ${v} of ${src}) `, ctx.block(() => [
|
||||
... converterFor(ctx, M.anonymousSimplePattern(p.pattern), v, vv =>
|
||||
[`${dest}.add(${vv})`, `continue`]),
|
||||
|
@ -175,14 +203,30 @@ export function converterForSimple(
|
|||
case 'dictof':
|
||||
return [`${dest} = void 0`,
|
||||
seq(`if (_.Dictionary.isDictionary<_embedded>(${src})) `, ctx.block(() => {
|
||||
const v = ctx.gentempname();
|
||||
const srcMap = ctx.gentempname();
|
||||
const resolver = ctx.mod.resolver();
|
||||
const kt = simpleType(resolver, p.key);
|
||||
const vt = simpleType(resolver, p.value);
|
||||
const k = ctx.gentempname();
|
||||
const v = ctx.gentempname();
|
||||
const symbolKeyed = isSymbolType(kt);
|
||||
return [
|
||||
seq(`${dest} = new _.KeyedDictionary()`),
|
||||
seq(`for (const [${k}, ${v}] of ${src}) `, ctx.block(() => [
|
||||
seq(`const ${srcMap} = new _.DictionaryMap(${src})`),
|
||||
(symbolKeyed
|
||||
? seq(`${dest} = {}`)
|
||||
: seq(`${dest} = new _.EncodableDictionary`,
|
||||
anglebrackets('_embedded', renderType(ctx.mod, kt), renderType(ctx.mod, vt)),
|
||||
parens(encoderForSimplePattern(ctx, p.key) ?? `k => k`,
|
||||
encoderForSimplePattern(ctx, p.value) ?? `v => v`))),
|
||||
seq(`for (const [${k}, ${v}] of ${srcMap}) `, ctx.block(() => [
|
||||
... converterFor(ctx, M.anonymousSimplePattern(p.key), k, kk =>
|
||||
converterFor(ctx, M.anonymousSimplePattern(p.value), v, vv =>
|
||||
[`${dest}.set(${kk}, ${vv})`, `continue`])),
|
||||
[
|
||||
(symbolKeyed
|
||||
? `${dest}[${kk}.description!] = ${vv}`
|
||||
: `${dest}.set(${kk}, ${vv})`),
|
||||
`continue`
|
||||
])),
|
||||
seq(`${dest} = void 0`),
|
||||
seq(`break`)]))];
|
||||
}))];
|
||||
|
@ -217,12 +261,13 @@ function converterForCompound(
|
|||
case 'tuplePrefix':
|
||||
return converterForTuple(ctx, p.fixed, src, knownArray, p.variable, ks);
|
||||
case 'dict': {
|
||||
const srcMap = ctx.gentempname();
|
||||
const entries = Array.from(p.entries);
|
||||
function loop(i: number): Item[] {
|
||||
if (i < entries.length) {
|
||||
const [k, n] = entries[i];
|
||||
const tmpSrc = ctx.gentemp();
|
||||
return [seq(`if ((${tmpSrc} = ${src}.get(${ctx.mod.literal(k)})) !== void 0) `,
|
||||
return [seq(`if ((${tmpSrc} = ${srcMap}.get(${ctx.mod.literal(k)})) !== void 0) `,
|
||||
ctx.block(() =>
|
||||
converterFor(
|
||||
ctx,
|
||||
|
@ -233,7 +278,9 @@ function converterForCompound(
|
|||
return ks();
|
||||
}
|
||||
}
|
||||
return [seq(`if (_.Dictionary.isDictionary<_embedded>(${src})) `, ctx.block(() => loop(0)))];
|
||||
return [seq(`if (_.Dictionary.isDictionary<_embedded>(${src})) `, ctx.block(() => [
|
||||
seq(`const ${srcMap} = new _.DictionaryMap(${src})`),
|
||||
... loop(0)]))];
|
||||
}
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as M from '../meta';
|
||||
import { block, braces, Item, keyvalue, parens, seq } from "./block";
|
||||
import { block, braces, Item, parens, seq } from "./block";
|
||||
import { FieldType, SimpleType, Type } from "./type";
|
||||
import { renderType } from "./rendertype";
|
||||
import { ModuleContext } from './context';
|
||||
import { ModuleContext, buildProduct } from './context';
|
||||
|
||||
export function genConstructor(
|
||||
mod: ModuleContext,
|
||||
|
@ -29,12 +29,7 @@ export function genConstructor(
|
|||
simpleValue = (variant === void 0) && (arg.kind !== 'unit');
|
||||
}
|
||||
|
||||
const initializers: Item[] = (variant !== void 0)
|
||||
? [keyvalue('_variant', JSON.stringify(variant))]
|
||||
: [];
|
||||
formals.forEach(([n, _t]) => initializers.push(seq(JSON.stringify(n), ': ', M.jsId(n))));
|
||||
|
||||
initializers.push(seq(`__as_preserve__() `, block(`return from${M.jsId(definitionName)}(this)`)));
|
||||
const initializers = formals.map(([n, _t]) => ({ fieldName: n, sourceExpr: M.jsId(n) }));
|
||||
|
||||
const declArgs: Array<Item> = (formals.length > 1)
|
||||
? [seq(braces(...formals.map(f => M.jsId(f[0]))), ': ',
|
||||
|
@ -48,7 +43,7 @@ export function genConstructor(
|
|||
seq(`return `,
|
||||
(simpleValue
|
||||
? 'value'
|
||||
: braces(...initializers))))),
|
||||
: buildProduct(definitionName, variant, initializers))))),
|
||||
seq(`${M.jsId(name)}.schema = function () `, block(
|
||||
seq(`return `, braces(
|
||||
`schema: _schema()`,
|
||||
|
|
|
@ -37,7 +37,6 @@ export function simpleType(resolver: RefResolver, p: M.SimplePattern): FieldType
|
|||
case 'atom':
|
||||
switch (p.atomKind._variant) {
|
||||
case 'Boolean': return Type.ref(`boolean`, null);
|
||||
case 'Float': return Type.ref(`number`, null);
|
||||
case 'Double': return Type.ref(`number`, null);
|
||||
case 'SignedInteger': return Type.ref(`number`, null);
|
||||
case 'String': return Type.ref(`string`, null);
|
||||
|
|
|
@ -40,14 +40,13 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string): Item {
|
|||
return `${src}`;
|
||||
case 'atom':
|
||||
switch (p.atomKind._variant) {
|
||||
case 'Float': return `_.Single(${src})`;
|
||||
case 'Double': return `_.Double(${src})`;
|
||||
default: return `${src}`;
|
||||
}
|
||||
case 'lit':
|
||||
return ctx.mod.literal(p.value);
|
||||
case 'embedded':
|
||||
return `_.embed(${src})`;
|
||||
return `${src}`;
|
||||
case 'seqof':
|
||||
return seq(`${src}.map(v => `,
|
||||
unconverterFor(ctx, M.Pattern.SimplePattern(p.pattern), 'v'),
|
||||
|
@ -58,8 +57,10 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string): Item {
|
|||
unconverterFor(ctx, M.Pattern.SimplePattern(p.pattern), 'v'),
|
||||
`)`)));
|
||||
case 'dictof':
|
||||
return seq(`new _.Dictionary<_embedded>`, parens(seq(
|
||||
`_.Array.from(${src}.entries()).map(([k, v]) => `,
|
||||
return seq(`_.Dictionary.from<_embedded>`, parens(seq(
|
||||
`_.Array.from(`,
|
||||
M.isSymbolPattern(p.key) ? `_.JsDictionary.entries(${src})` : `${src}.entries()`,
|
||||
`).map(([k, v]) => `,
|
||||
brackets(
|
||||
unconverterFor(ctx, M.Pattern.SimplePattern(p.key), 'k'),
|
||||
unconverterFor(ctx, M.Pattern.SimplePattern(p.value), 'v')),
|
||||
|
@ -96,7 +97,7 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string): Item {
|
|||
}
|
||||
}
|
||||
case 'dict':
|
||||
return seq(`new _.Dictionary<_embedded>`, parens(
|
||||
return seq(`_.Dictionary.from<_embedded>`, parens(
|
||||
brackets(... Array.from(p.entries.entries()).map(([k, n]) =>
|
||||
brackets(
|
||||
ctx.mod.literal(k),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SimpleType, Type } from "./type";
|
||||
import { isSymbolType, SimpleType, Type } from "./type";
|
||||
import { anglebrackets, braces, Item, keyvalue, opseq, seq } from "./block";
|
||||
import { ModuleContext } from "./context";
|
||||
|
||||
|
@ -10,7 +10,7 @@ export function variantFor(variantName: string): Item {
|
|||
return keyvalue('_variant', JSON.stringify(variantName));
|
||||
}
|
||||
|
||||
function simpleTypeFields(ctxt: ModuleContext, baseType: Type, t: SimpleType): Item[] {
|
||||
function simpleTypeFields(ctxt: ModuleContext, t: SimpleType): Item[] {
|
||||
switch (t.kind) {
|
||||
case 'unit':
|
||||
return [];
|
||||
|
@ -29,35 +29,54 @@ function simpleTypeFields(ctxt: ModuleContext, baseType: Type, t: SimpleType): I
|
|||
|
||||
export function renderVariant(
|
||||
ctxt: ModuleContext,
|
||||
baseType: Type,
|
||||
[variantName, t]: [string, SimpleType],
|
||||
): Item {
|
||||
let fields = simpleTypeFields(ctxt, baseType, t);
|
||||
let fields = simpleTypeFields(ctxt, t);
|
||||
return braces(variantFor(variantName), ... fields);
|
||||
}
|
||||
|
||||
export function renderType(ctxt: ModuleContext, t: Type): Item {
|
||||
switch (t.kind) {
|
||||
case 'union': return opseq('never', ' | ', ...
|
||||
Array.from(t.variants).flatMap(entry => renderVariant(ctxt, t, entry)));
|
||||
case 'unit': return braces(... simpleTypeFields(ctxt, t, t));
|
||||
Array.from(t.variants).flatMap(entry => renderVariant(ctxt, entry)));
|
||||
case 'unit': return braces(... simpleTypeFields(ctxt, t));
|
||||
case 'ref':
|
||||
if (t.ref === null && t.typeName === '_embedded') {
|
||||
return t.typeName;
|
||||
} else {
|
||||
return seq(t.typeName, ctxt.genericArgsFor(t));
|
||||
}
|
||||
case 'set': return seq('_.KeyedSet', anglebrackets(
|
||||
renderType(ctxt, t.type),
|
||||
'_embedded'));
|
||||
case 'dictionary': return seq('_.KeyedDictionary', anglebrackets(
|
||||
renderType(ctxt, t.key),
|
||||
renderType(ctxt, t.value),
|
||||
'_embedded'));
|
||||
case 'set': return seq('_.EncodableSet', anglebrackets(
|
||||
'_embedded',
|
||||
renderType(ctxt, t.type)));
|
||||
case 'dictionary':
|
||||
if (isSymbolType(t.key)) {
|
||||
return seq('_.JsDictionary', anglebrackets(renderType(ctxt, t.value)));
|
||||
} else {
|
||||
return seq('_.EncodableDictionary', anglebrackets(
|
||||
'_embedded',
|
||||
renderType(ctxt, t.key),
|
||||
renderType(ctxt, t.value)));
|
||||
}
|
||||
case 'array': return seq('Array', anglebrackets(renderType(ctxt, t.type)));
|
||||
case 'record': return braces(... simpleTypeFields(ctxt, t, t));
|
||||
case 'record': return braces(... simpleTypeFields(ctxt, t));
|
||||
default:
|
||||
((_: never) => {})(t);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
export function renderTypeWithConversionMixins(ctxt: ModuleContext, t: Type): Item {
|
||||
if (t.kind === 'unit' || t.kind === 'record' || t.kind === 'union') {
|
||||
return opseq('any', ' & ',
|
||||
renderType(ctxt, t),
|
||||
seq('_.Preservable', ctxt.hasEmbedded(t) ? ctxt.genericArgs() : '<any>'),
|
||||
seq('_.PreserveWritable', ctxt.hasEmbedded(t) ? ctxt.genericArgs() : '<any>'),
|
||||
braces(seq('__as_preserve__',
|
||||
ctxt.hasEmbedded(t) ? '' : ctxt.genericParameters(),
|
||||
'()',
|
||||
': _.Value', ctxt.genericArgs())));
|
||||
} else {
|
||||
return renderType(ctxt, t);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,3 +35,7 @@ export namespace Type {
|
|||
}
|
||||
|
||||
export const ANY_TYPE: FieldType = Type.ref('_.Value', null);
|
||||
|
||||
export function isSymbolType(ty: FieldType): ty is { kind: 'ref', typeName: 'symbol', ref: null } {
|
||||
return ty.kind === 'ref' && ty.typeName === 'symbol' && ty.ref === null;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue