Compare commits
228 Commits
rust-prese
...
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 |
|
@ -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.990.0"
|
||||
version_date: "March 2024"
|
||||
version: "0.995.0"
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
[
|
||||
{"version":"0.990.0","title":"0.990.0","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,30 +1,51 @@
|
|||
For a value `V`, we write `«V»` for the binary encoding of `V`.
|
||||
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>.
|
||||
|
||||
«#f» = [0x80]
|
||||
«#t» = [0x81]
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
«`#f`» | = | `80`
|
||||
«`#t`» | = | `81`
|
||||
|
||||
«@W V» = [0x85] ++ «W» ++ «V»
|
||||
«#!V» = [0x86] ++ «V»
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
«`@`*W* *V*» | = | `85` «*W*» «*V*»
|
||||
«`#:`*V*» | = | `86` «*V*»
|
||||
|
||||
«V» if V ∈ Float = [0x87, 0x04] ++ binary32(V)
|
||||
«V» if V ∈ Double = [0x87, 0x08] ++ binary64(V)
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
«*V*» | = | `87``08` **binary64**(*V*) | if *V* ∈ Double
|
||||
|
||||
«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)
|
||||
{:.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
|
||||
|
||||
«<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]
|
||||
{:.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`
|
||||
|
||||
varint(v) = [v] if v < 128
|
||||
[(v & 0x7F) + 128] ++ varint(v >> 7) if v ≥ 128
|
||||
{:.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
|
||||
|
||||
The functions `binary32(F)` and `binary64(D)` yield big-endian 4- and
|
||||
8-byte IEEE 754 binary representations of `F` and `D`, respectively.
|
||||
{:.postcard-grammar.binarysyntax}
|
||||
**intbytes**(*n*) | = | <span class="roman">the empty sequence if</span> *n* = 0<span class="roman">, otherwise</span> **signedBigEndian**(*n*)
|
||||
|
||||
The function `intbytes(x)` is a big-endian two's-complement signed binary representation of
|
||||
`x`, taking exactly as many whole bytes as needed to unambiguously identify the value and its
|
||||
sign. In particular, `intbytes(0)` is the empty byte sequence.
|
||||
{:.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)
|
||||
|
|
|
@ -261,7 +261,6 @@ PRESERVES_OUTOFLINE
|
|||
|
||||
typedef enum preserves_type_tag {
|
||||
PRESERVES_BOOLEAN = 0,
|
||||
PRESERVES_FLOAT,
|
||||
PRESERVES_DOUBLE,
|
||||
|
||||
PRESERVES_SIGNED_INTEGER,
|
||||
|
@ -283,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";
|
||||
|
@ -366,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 +415,6 @@ typedef struct preserves_index_entry {
|
|||
|
||||
union {
|
||||
bool _boolean;
|
||||
float _float;
|
||||
double _double;
|
||||
int64_t _signed;
|
||||
uint64_t _unsigned;
|
||||
|
@ -818,18 +814,6 @@ PRESERVES_OUTOFLINE
|
|||
uint8_t *bs = _preserves_reader_next_bytes(r, len);
|
||||
if (bs == NULL) return _preserves_reader_finish(r, PRESERVES_END_INCOMPLETE_INPUT);
|
||||
switch (len) {
|
||||
case 4: {
|
||||
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 8: {
|
||||
uint32_t lo, hi;
|
||||
memcpy(&hi, bs, 4);
|
||||
|
@ -995,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;
|
||||
|
|
|
@ -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;
|
||||
|
@ -113,7 +104,6 @@ namespace Preserves {
|
|||
return BinaryReader<>(i).next().map(decodeEmbedded).map(Value<T>::from_embedded);
|
||||
case BinaryTag::Ieee754: return varint(i).flat_map([&](size_t len)-> boost::optional<Value<T>> {
|
||||
switch (len) {
|
||||
case 4: return next_float().map(Value<T>::from_float);
|
||||
case 8: return next_double().map(Value<T>::from_double);
|
||||
default: return boost::none;
|
||||
}
|
||||
|
|
|
@ -151,19 +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::Ieee754;
|
||||
put(uint8_t(sizeof(buf)));
|
||||
return write(buf, sizeof(buf));
|
||||
}
|
||||
|
||||
BinaryWriter& operator<<(double d) {
|
||||
uint64_t n;
|
||||
memcpy(&n, &d, sizeof(d));
|
||||
|
|
|
@ -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(); }
|
||||
|
@ -355,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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@preserves/core",
|
||||
"version": "0.990.0",
|
||||
"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,24 +1,25 @@
|
|||
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;
|
||||
|
@ -26,15 +27,14 @@ export interface TypedDecoder<T> {
|
|||
|
||||
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
|
||||
{
|
||||
|
@ -130,20 +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;
|
||||
}
|
||||
|
||||
wrap<T>(v: Value<T>): Value<T> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -161,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>;
|
||||
|
||||
|
@ -191,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> {
|
||||
|
@ -211,10 +239,9 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
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 4: return this.state.wrap<T>(SingleFloat.fromBytes(this.state.nextbytes(4)));
|
||||
case 8: return this.state.wrap<T>(DoubleFloat.fromBytes(this.state.nextbytes(8)));
|
||||
default: throw new DecodeError("Invalid IEEE754 size");
|
||||
}
|
||||
|
@ -228,7 +255,14 @@ 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.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>(s);
|
||||
}
|
||||
case Tag.Dictionary: return this.state.wrap<T>(Decoder.dictionaryFromArray(this.nextvalues()));
|
||||
default: throw new DecodeError("Unsupported Preserves tag: " + tag);
|
||||
}
|
||||
|
@ -255,7 +289,7 @@ 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
|
||||
{
|
||||
|
@ -281,14 +315,6 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
});
|
||||
}
|
||||
|
||||
nextFloat(): SingleFloat | undefined {
|
||||
return this.skipAnnotations((reset) => {
|
||||
if (this.state.nextbyte() !== Tag.Ieee754) return reset();
|
||||
if (this.state.nextbyte() !== 4) return reset();
|
||||
return SingleFloat.fromBytes(this.state.nextbytes(4));
|
||||
});
|
||||
}
|
||||
|
||||
nextDouble(): DoubleFloat | undefined {
|
||||
return this.skipAnnotations((reset) => {
|
||||
if (this.state.nextbyte() !== Tag.Ieee754) return reset();
|
||||
|
@ -297,16 +323,16 @@ export class Decoder<T = never> implements TypedDecoder<T> {
|
|||
});
|
||||
}
|
||||
|
||||
nextEmbedded(): Embedded<T> | undefined {
|
||||
nextEmbedded(): T | undefined {
|
||||
return this.skipAnnotations((reset) => {
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.Embedded: return embed(this.embeddedDecode.decode(this.state));
|
||||
case Tag.Embedded: return this.embeddedDecode.decode(this.state);
|
||||
default: return reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextSignedInteger(): number | undefined {
|
||||
nextSignedInteger(): number | bigint | undefined {
|
||||
return this.skipAnnotations((reset) => {
|
||||
switch (this.state.nextbyte()) {
|
||||
case Tag.SignedInteger: return this.state.nextint(this.state.varint());
|
||||
|
@ -369,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,8 +138,9 @@ export class EncoderState {
|
|||
this.emitbyte(v);
|
||||
}
|
||||
|
||||
encodeint(v: number) {
|
||||
// TODO: Bignums :-/
|
||||
encodeint(v: number | bigint) {
|
||||
if (typeof v === 'bigint') return this.encodebigint(v);
|
||||
|
||||
this.emitbyte(Tag.SignedInteger);
|
||||
|
||||
if (v === 0) {
|
||||
|
@ -153,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);
|
||||
|
@ -160,7 +200,7 @@ export class EncoderState {
|
|||
}
|
||||
}
|
||||
|
||||
export class Encoder<T = object> {
|
||||
export class Encoder<T extends Embeddable> {
|
||||
state: EncoderState;
|
||||
embeddedEncode: EmbeddedTypeEncode<T>;
|
||||
|
||||
|
@ -179,7 +219,7 @@ export class Encoder<T = object> {
|
|||
}
|
||||
}
|
||||
|
||||
withEmbeddedEncode<S>(
|
||||
withEmbeddedEncode<S extends Embeddable>(
|
||||
embeddedEncode: EmbeddedTypeEncode<S>,
|
||||
body: (e: Encoder<S>) => void): this
|
||||
{
|
||||
|
@ -203,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)) {
|
||||
|
@ -219,7 +259,7 @@ export class Encoder<T = object> {
|
|||
else if (typeof v === 'boolean') {
|
||||
this.state.emitbyte(v ? Tag.True : Tag.False);
|
||||
}
|
||||
else if (typeof v === 'number') {
|
||||
else if (typeof v === 'number' || typeof v === 'bigint') {
|
||||
this.state.encodeint(v);
|
||||
}
|
||||
else if (typeof v === 'string') {
|
||||
|
@ -245,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
|
||||
{
|
||||
|
@ -291,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,79 +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.Ieee754);
|
||||
encoder.state.emitbyte(4);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -139,7 +101,7 @@ 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;
|
||||
}
|
||||
|
||||
|
@ -157,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,7 +77,7 @@ 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
|
||||
|
@ -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);
|
||||
|
@ -141,24 +138,24 @@ describe('encoding and decoding embeddeds', () => {
|
|||
.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.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.SignedInteger, 0,
|
||||
|
@ -167,21 +164,86 @@ describe('encoding and decoding embeddeds', () => {
|
|||
]), { 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.990.0",
|
||||
"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.990.0",
|
||||
"@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;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { Annotated, Bytes, Set, Dictionary, Fold, fold, Record, Tuple, Value, stringify, Embedded } from "@preserves/core";
|
||||
import { Annotated, Bytes, Set, Fold, fold, Record, Tuple, Value, stringify, DictionaryMap } from "@preserves/core";
|
||||
import { brackets, Item, parens, seq } from "./block";
|
||||
import * as M from '../meta';
|
||||
|
||||
export function sourceCodeFor(v: Value<M.InputEmbedded>): Item {
|
||||
return fold(v, {
|
||||
boolean(b: boolean): Item { return b.toString(); },
|
||||
single(f: number): Item { return f.toString(); },
|
||||
double(f: number): Item { return f.toString(); },
|
||||
integer(i: number): Item { return i.toString(); },
|
||||
string(s: string): Item { return JSON.stringify(s); },
|
||||
|
@ -23,8 +22,8 @@ export function sourceCodeFor(v: Value<M.InputEmbedded>): Item {
|
|||
set(s: Set<M.InputEmbedded>, k: Fold<M.InputEmbedded, Item>): Item {
|
||||
return seq('new _.Set<_.Value<_embedded>>', parens(brackets(... Array.from(s).map(k))));
|
||||
},
|
||||
dictionary(d: Dictionary<M.InputEmbedded>, k: Fold<M.InputEmbedded, Item>): Item {
|
||||
return seq('new _.Dictionary<_embedded>', parens(brackets(... Array.from(d).map(([kk,vv]) =>
|
||||
dictionary(d: DictionaryMap<M.InputEmbedded>, k: Fold<M.InputEmbedded, Item>): Item {
|
||||
return seq('_.Dictionary.from<_embedded>', parens(brackets(... Array.from(d).map(([kk,vv]) =>
|
||||
brackets(k(kk), k(vv))))));
|
||||
},
|
||||
|
||||
|
@ -32,7 +31,7 @@ export function sourceCodeFor(v: Value<M.InputEmbedded>): Item {
|
|||
return seq('_.annotate<_embedded>', parens(k(a.item), ... a.annotations.map(k)));
|
||||
},
|
||||
|
||||
embedded(t: Embedded<M.InputEmbedded>, _k: Fold<M.InputEmbedded, Item>): Item {
|
||||
embedded(t: M.InputEmbedded, _k: Fold<M.InputEmbedded, Item>): Item {
|
||||
throw new Error(`Cannot emit source code for construction of embedded ${stringify(t)}`);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -0,0 +1,716 @@
|
|||
import * as _ from "@preserves/core";
|
||||
import * as _i_schema from "./schema";
|
||||
|
||||
export const $any = _.Symbol.for("any");
|
||||
export const $array = _.Symbol.for("array");
|
||||
export const $embedded = _.Symbol.for("embedded");
|
||||
export const $map = _.Symbol.for("map");
|
||||
export const $rec = _.Symbol.for("rec");
|
||||
export const $ref = _.Symbol.for("ref");
|
||||
export const $set = _.Symbol.for("set");
|
||||
export const $union = _.Symbol.for("union");
|
||||
export const $unit = _.Symbol.for("unit");
|
||||
|
||||
let __schema: _.Value | null = null;
|
||||
|
||||
export function _schema() {
|
||||
if (__schema === null) {
|
||||
__schema = _.decode<_.GenericEmbedded>(_.Bytes.fromHex("b4b306736368656d61b7b30776657273696f6eb00101b30b646566696e6974696f6e73b7b3054669656c64b4b3026f72b5b5b104756e6974b4b3036c6974b304756e69748484b5b103616e79b4b3036c6974b303616e798484b5b108656d626564646564b4b3036c6974b308656d6265646465648484b5b1056172726179b4b303726563b4b3036c6974b305617272617984b4b3057475706c65b5b4b3056e616d6564b307656c656d656e74b4b303726566b584b3054669656c64848484848484b5b103736574b4b303726563b4b3036c6974b30373657484b4b3057475706c65b5b4b3056e616d6564b307656c656d656e74b4b303726566b584b3054669656c64848484848484b5b1036d6170b4b303726563b4b3036c6974b3036d617084b4b3057475706c65b5b4b3056e616d6564b3036b6579b4b303726566b584b3054669656c648484b4b3056e616d6564b30576616c7565b4b303726566b584b3054669656c64848484848484b5b103726566b4b303726563b4b3036c6974b30372656684b4b3057475706c65b5b4b3056e616d6564b3046e616d65b4b303726566b5b306736368656d6184b303526566848484848484b5b10841746f6d4b696e64b4b303726566b5b306736368656d6184b30841746f6d4b696e6484848484b3065265636f7264b4b303726563b4b3036c6974b30372656384b4b3057475706c65b5b4b3056e616d6564b3066669656c6473b4b3057365716f66b4b303726566b584b30a4e616d65644669656c64848484848484b30653696d706c65b4b3026f72b5b5b1054669656c64b4b303726566b584b3054669656c648484b5b1065265636f7264b4b303726566b584b3065265636f726484848484b30756617269616e74b4b3057475706c65b5b4b3056e616d6564b3056c6162656cb4b30461746f6db30653796d626f6c8484b4b3056e616d6564b30474797065b4b303726566b584b30653696d706c6584848484b30a446566696e6974696f6eb4b3026f72b5b5b105756e696f6eb4b303726563b4b3036c6974b305756e696f6e84b4b3057475706c65b5b4b3056e616d6564b30876617269616e7473b4b3057365716f66b4b303726566b584b30756617269616e7484848484848484b5b10653696d706c65b4b303726566b584b30653696d706c6584848484b30a4e616d65644669656c64b4b3057475706c65b5b4b3056e616d6564b3046e616d65b4b30461746f6db30653796d626f6c8484b4b3056e616d6564b30474797065b4b303726566b584b3054669656c648484848484b30c656d62656464656454797065808484"));
|
||||
};
|
||||
return __schema;
|
||||
}
|
||||
|
||||
export const _imports = {"schema": _i_schema}
|
||||
|
||||
|
||||
export type Definition = (
|
||||
(
|
||||
{"_variant": "union", "variants": Array<Variant>} |
|
||||
{"_variant": "Simple", "value": Simple}
|
||||
) &
|
||||
_.Preservable<any> &
|
||||
_.PreserveWritable<any> &
|
||||
{
|
||||
__as_preserve__<_embedded extends _.Embeddable = _.GenericEmbedded>(): _.Value<_embedded>
|
||||
}
|
||||
);
|
||||
|
||||
export type Variant = (
|
||||
{"label": symbol, "type": Simple} &
|
||||
_.Preservable<any> &
|
||||
_.PreserveWritable<any> &
|
||||
{
|
||||
__as_preserve__<_embedded extends _.Embeddable = _.GenericEmbedded>(): _.Value<_embedded>
|
||||
}
|
||||
);
|
||||
|
||||
export type Simple = (
|
||||
(
|
||||
{"_variant": "Field", "value": Field} |
|
||||
{"_variant": "Record", "value": Record}
|
||||
) &
|
||||
_.Preservable<any> &
|
||||
_.PreserveWritable<any> &
|
||||
{
|
||||
__as_preserve__<_embedded extends _.Embeddable = _.GenericEmbedded>(): _.Value<_embedded>
|
||||
}
|
||||
);
|
||||
|
||||
export type Record = (
|
||||
{"fields": Array<NamedField>} &
|
||||
_.Preservable<any> &
|
||||
_.PreserveWritable<any> &
|
||||
{
|
||||
__as_preserve__<_embedded extends _.Embeddable = _.GenericEmbedded>(): _.Value<_embedded>
|
||||
}
|
||||
);
|
||||
|
||||
export type NamedField = (
|
||||
{"name": symbol, "type": Field} &
|
||||
_.Preservable<any> &
|
||||
_.PreserveWritable<any> &
|
||||
{
|
||||
__as_preserve__<_embedded extends _.Embeddable = _.GenericEmbedded>(): _.Value<_embedded>
|
||||
}
|
||||
);
|
||||
|
||||
export type Field = (
|
||||
(
|
||||
{"_variant": "unit"} |
|
||||
{"_variant": "any"} |
|
||||
{"_variant": "embedded"} |
|
||||
{"_variant": "array", "element": Field} |
|
||||
{"_variant": "set", "element": Field} |
|
||||
{"_variant": "map", "key": Field, "value": Field} |
|
||||
{"_variant": "ref", "name": _i_schema.Ref} |
|
||||
{"_variant": "AtomKind", "value": _i_schema.AtomKind}
|
||||
) &
|
||||
_.Preservable<any> &
|
||||
_.PreserveWritable<any> &
|
||||
{
|
||||
__as_preserve__<_embedded extends _.Embeddable = _.GenericEmbedded>(): _.Value<_embedded>
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
export namespace Definition {
|
||||
export function union(variants: Array<Variant>): Definition {
|
||||
return {
|
||||
"_variant": "union",
|
||||
"variants": variants,
|
||||
__as_preserve__() {return fromDefinition(this);},
|
||||
__preserve_on__(e) { e.push(fromDefinition(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromDefinition(this)); }
|
||||
};
|
||||
};
|
||||
union.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Definition"),
|
||||
variant: _.Symbol.for("union")
|
||||
};
|
||||
};
|
||||
export function Simple(value: Simple): Definition {
|
||||
return {
|
||||
"_variant": "Simple",
|
||||
"value": value,
|
||||
__as_preserve__() {return fromDefinition(this);},
|
||||
__preserve_on__(e) { e.push(fromDefinition(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromDefinition(this)); }
|
||||
};
|
||||
};
|
||||
Simple.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Definition"),
|
||||
variant: _.Symbol.for("Simple")
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function Variant({label, type}: {label: symbol, type: Simple}): Variant {
|
||||
return {
|
||||
"label": label,
|
||||
"type": type,
|
||||
__as_preserve__() {return fromVariant(this);},
|
||||
__preserve_on__(e) { e.push(fromVariant(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromVariant(this)); }
|
||||
};
|
||||
}
|
||||
|
||||
Variant.schema = function () {
|
||||
return {schema: _schema(), imports: _imports, definitionName: _.Symbol.for("Variant")};
|
||||
}
|
||||
|
||||
export namespace Simple {
|
||||
export function Field(value: Field): Simple {
|
||||
return {
|
||||
"_variant": "Field",
|
||||
"value": value,
|
||||
__as_preserve__() {return fromSimple(this);},
|
||||
__preserve_on__(e) { e.push(fromSimple(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromSimple(this)); }
|
||||
};
|
||||
};
|
||||
Field.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Simple"),
|
||||
variant: _.Symbol.for("Field")
|
||||
};
|
||||
};
|
||||
export function Record(value: Record): Simple {
|
||||
return {
|
||||
"_variant": "Record",
|
||||
"value": value,
|
||||
__as_preserve__() {return fromSimple(this);},
|
||||
__preserve_on__(e) { e.push(fromSimple(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromSimple(this)); }
|
||||
};
|
||||
};
|
||||
Record.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Simple"),
|
||||
variant: _.Symbol.for("Record")
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function Record(fields: Array<NamedField>): Record {
|
||||
return {
|
||||
"fields": fields,
|
||||
__as_preserve__() {return fromRecord(this);},
|
||||
__preserve_on__(e) { e.push(fromRecord(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromRecord(this)); }
|
||||
};
|
||||
}
|
||||
|
||||
Record.schema = function () {
|
||||
return {schema: _schema(), imports: _imports, definitionName: _.Symbol.for("Record")};
|
||||
}
|
||||
|
||||
export function NamedField({name, type}: {name: symbol, type: Field}): NamedField {
|
||||
return {
|
||||
"name": name,
|
||||
"type": type,
|
||||
__as_preserve__() {return fromNamedField(this);},
|
||||
__preserve_on__(e) { e.push(fromNamedField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromNamedField(this)); }
|
||||
};
|
||||
}
|
||||
|
||||
NamedField.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("NamedField")
|
||||
};
|
||||
}
|
||||
|
||||
export namespace Field {
|
||||
export function unit(): Field {
|
||||
return {
|
||||
"_variant": "unit",
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
unit.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Field"),
|
||||
variant: _.Symbol.for("unit")
|
||||
};
|
||||
};
|
||||
export function any(): Field {
|
||||
return {
|
||||
"_variant": "any",
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
any.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Field"),
|
||||
variant: _.Symbol.for("any")
|
||||
};
|
||||
};
|
||||
export function embedded(): Field {
|
||||
return {
|
||||
"_variant": "embedded",
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
embedded.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Field"),
|
||||
variant: _.Symbol.for("embedded")
|
||||
};
|
||||
};
|
||||
export function array(element: Field): Field {
|
||||
return {
|
||||
"_variant": "array",
|
||||
"element": element,
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
array.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Field"),
|
||||
variant: _.Symbol.for("array")
|
||||
};
|
||||
};
|
||||
export function set(element: Field): Field {
|
||||
return {
|
||||
"_variant": "set",
|
||||
"element": element,
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
set.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Field"),
|
||||
variant: _.Symbol.for("set")
|
||||
};
|
||||
};
|
||||
export function map({key, value}: {key: Field, value: Field}): Field {
|
||||
return {
|
||||
"_variant": "map",
|
||||
"key": key,
|
||||
"value": value,
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
map.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Field"),
|
||||
variant: _.Symbol.for("map")
|
||||
};
|
||||
};
|
||||
export function ref(name: _i_schema.Ref): Field {
|
||||
return {
|
||||
"_variant": "ref",
|
||||
"name": name,
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
ref.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Field"),
|
||||
variant: _.Symbol.for("ref")
|
||||
};
|
||||
};
|
||||
export function AtomKind(value: _i_schema.AtomKind): Field {
|
||||
return {
|
||||
"_variant": "AtomKind",
|
||||
"value": value,
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
AtomKind.schema = function () {
|
||||
return {
|
||||
schema: _schema(),
|
||||
imports: _imports,
|
||||
definitionName: _.Symbol.for("Field"),
|
||||
variant: _.Symbol.for("AtomKind")
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function asDefinition<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): Definition {
|
||||
let result = toDefinition(v);
|
||||
if (result === void 0) throw new TypeError(`Invalid Definition: ${_.stringify(v)}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toDefinition<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | Definition {
|
||||
let result: undefined | Definition;
|
||||
if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(v)) {
|
||||
let _tmp0: ({}) | undefined;
|
||||
_tmp0 = _.is(v.label, $union) ? {} : void 0;
|
||||
if (_tmp0 !== void 0) {
|
||||
let _tmp1: (Array<Variant>) | undefined;
|
||||
_tmp1 = void 0;
|
||||
if (_.isSequence(v[0])) {
|
||||
_tmp1 = [];
|
||||
for (const _tmp2 of v[0]) {
|
||||
let _tmp3: (Variant) | undefined;
|
||||
_tmp3 = toVariant(_tmp2);
|
||||
if (_tmp3 !== void 0) {_tmp1.push(_tmp3); continue;};
|
||||
_tmp1 = void 0;
|
||||
break;
|
||||
};
|
||||
};
|
||||
if (_tmp1 !== void 0) {
|
||||
result = {
|
||||
"_variant": "union",
|
||||
"variants": _tmp1,
|
||||
__as_preserve__() {return fromDefinition(this);},
|
||||
__preserve_on__(e) { e.push(fromDefinition(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromDefinition(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (result === void 0) {
|
||||
let _tmp4: (Simple) | undefined;
|
||||
_tmp4 = toSimple(v);
|
||||
if (_tmp4 !== void 0) {
|
||||
result = {
|
||||
"_variant": "Simple",
|
||||
"value": _tmp4,
|
||||
__as_preserve__() {return fromDefinition(this);},
|
||||
__preserve_on__(e) { e.push(fromDefinition(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromDefinition(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
export namespace Definition {export const __from_preserve__ = toDefinition;}
|
||||
|
||||
export function fromDefinition<_embedded extends _.Embeddable = _.GenericEmbedded>(_v: Definition): _.Value<_embedded> {
|
||||
switch (_v._variant) {
|
||||
case "union": {return _.Record($union, [_v["variants"].map(v => fromVariant<_embedded>(v))]);};
|
||||
case "Simple": {return fromSimple<_embedded>(_v.value);};
|
||||
};
|
||||
}
|
||||
|
||||
export function asVariant<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): Variant {
|
||||
let result = toVariant(v);
|
||||
if (result === void 0) throw new TypeError(`Invalid Variant: ${_.stringify(v)}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toVariant<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | Variant {
|
||||
let result: undefined | Variant;
|
||||
if (_.isSequence(v) && v.length >= 2) {
|
||||
let _tmp0: (symbol) | undefined;
|
||||
_tmp0 = typeof v[0] === 'symbol' ? v[0] : void 0;
|
||||
if (_tmp0 !== void 0) {
|
||||
let _tmp1: (Simple) | undefined;
|
||||
_tmp1 = toSimple(v[1]);
|
||||
if (_tmp1 !== void 0) {
|
||||
result = {
|
||||
"label": _tmp0,
|
||||
"type": _tmp1,
|
||||
__as_preserve__() {return fromVariant(this);},
|
||||
__preserve_on__(e) { e.push(fromVariant(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromVariant(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
Variant.__from_preserve__ = toVariant;
|
||||
|
||||
export function fromVariant<_embedded extends _.Embeddable = _.GenericEmbedded>(_v: Variant): _.Value<_embedded> {return [_v["label"], fromSimple<_embedded>(_v["type"])];}
|
||||
|
||||
export function asSimple<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): Simple {
|
||||
let result = toSimple(v);
|
||||
if (result === void 0) throw new TypeError(`Invalid Simple: ${_.stringify(v)}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toSimple<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | Simple {
|
||||
let _tmp0: (Field) | undefined;
|
||||
let result: undefined | Simple;
|
||||
_tmp0 = toField(v);
|
||||
if (_tmp0 !== void 0) {
|
||||
result = {
|
||||
"_variant": "Field",
|
||||
"value": _tmp0,
|
||||
__as_preserve__() {return fromSimple(this);},
|
||||
__preserve_on__(e) { e.push(fromSimple(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromSimple(this)); }
|
||||
};
|
||||
};
|
||||
if (result === void 0) {
|
||||
let _tmp1: (Record) | undefined;
|
||||
_tmp1 = toRecord(v);
|
||||
if (_tmp1 !== void 0) {
|
||||
result = {
|
||||
"_variant": "Record",
|
||||
"value": _tmp1,
|
||||
__as_preserve__() {return fromSimple(this);},
|
||||
__preserve_on__(e) { e.push(fromSimple(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromSimple(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
export namespace Simple {export const __from_preserve__ = toSimple;}
|
||||
|
||||
export function fromSimple<_embedded extends _.Embeddable = _.GenericEmbedded>(_v: Simple): _.Value<_embedded> {
|
||||
switch (_v._variant) {
|
||||
case "Field": {return fromField<_embedded>(_v.value);};
|
||||
case "Record": {return fromRecord<_embedded>(_v.value);};
|
||||
};
|
||||
}
|
||||
|
||||
export function asRecord<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): Record {
|
||||
let result = toRecord(v);
|
||||
if (result === void 0) throw new TypeError(`Invalid Record: ${_.stringify(v)}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toRecord<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | Record {
|
||||
let result: undefined | Record;
|
||||
if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(v)) {
|
||||
let _tmp0: ({}) | undefined;
|
||||
_tmp0 = _.is(v.label, $rec) ? {} : void 0;
|
||||
if (_tmp0 !== void 0) {
|
||||
let _tmp1: (Array<NamedField>) | undefined;
|
||||
_tmp1 = void 0;
|
||||
if (_.isSequence(v[0])) {
|
||||
_tmp1 = [];
|
||||
for (const _tmp2 of v[0]) {
|
||||
let _tmp3: (NamedField) | undefined;
|
||||
_tmp3 = toNamedField(_tmp2);
|
||||
if (_tmp3 !== void 0) {_tmp1.push(_tmp3); continue;};
|
||||
_tmp1 = void 0;
|
||||
break;
|
||||
};
|
||||
};
|
||||
if (_tmp1 !== void 0) {
|
||||
result = {
|
||||
"fields": _tmp1,
|
||||
__as_preserve__() {return fromRecord(this);},
|
||||
__preserve_on__(e) { e.push(fromRecord(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromRecord(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
Record.__from_preserve__ = toRecord;
|
||||
|
||||
export function fromRecord<_embedded extends _.Embeddable = _.GenericEmbedded>(_v: Record): _.Value<_embedded> {return _.Record($rec, [_v["fields"].map(v => fromNamedField<_embedded>(v))]);}
|
||||
|
||||
export function asNamedField<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): NamedField {
|
||||
let result = toNamedField(v);
|
||||
if (result === void 0) throw new TypeError(`Invalid NamedField: ${_.stringify(v)}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toNamedField<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | NamedField {
|
||||
let result: undefined | NamedField;
|
||||
if (_.isSequence(v) && v.length >= 2) {
|
||||
let _tmp0: (symbol) | undefined;
|
||||
_tmp0 = typeof v[0] === 'symbol' ? v[0] : void 0;
|
||||
if (_tmp0 !== void 0) {
|
||||
let _tmp1: (Field) | undefined;
|
||||
_tmp1 = toField(v[1]);
|
||||
if (_tmp1 !== void 0) {
|
||||
result = {
|
||||
"name": _tmp0,
|
||||
"type": _tmp1,
|
||||
__as_preserve__() {return fromNamedField(this);},
|
||||
__preserve_on__(e) { e.push(fromNamedField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromNamedField(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
NamedField.__from_preserve__ = toNamedField;
|
||||
|
||||
export function fromNamedField<_embedded extends _.Embeddable = _.GenericEmbedded>(_v: NamedField): _.Value<_embedded> {return [_v["name"], fromField<_embedded>(_v["type"])];}
|
||||
|
||||
export function asField<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): Field {
|
||||
let result = toField(v);
|
||||
if (result === void 0) throw new TypeError(`Invalid Field: ${_.stringify(v)}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toField<_embedded extends _.Embeddable = _.GenericEmbedded>(v: _.Value<_embedded>): undefined | Field {
|
||||
let _tmp0: ({}) | undefined;
|
||||
let result: undefined | Field;
|
||||
_tmp0 = _.is(v, $unit) ? {} : void 0;
|
||||
if (_tmp0 !== void 0) {
|
||||
result = {
|
||||
"_variant": "unit",
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
if (result === void 0) {
|
||||
let _tmp1: ({}) | undefined;
|
||||
_tmp1 = _.is(v, $any) ? {} : void 0;
|
||||
if (_tmp1 !== void 0) {
|
||||
result = {
|
||||
"_variant": "any",
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
if (result === void 0) {
|
||||
let _tmp2: ({}) | undefined;
|
||||
_tmp2 = _.is(v, $embedded) ? {} : void 0;
|
||||
if (_tmp2 !== void 0) {
|
||||
result = {
|
||||
"_variant": "embedded",
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
if (result === void 0) {
|
||||
if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(v)) {
|
||||
let _tmp3: ({}) | undefined;
|
||||
_tmp3 = _.is(v.label, $array) ? {} : void 0;
|
||||
if (_tmp3 !== void 0) {
|
||||
let _tmp4: (Field) | undefined;
|
||||
_tmp4 = toField(v[0]);
|
||||
if (_tmp4 !== void 0) {
|
||||
result = {
|
||||
"_variant": "array",
|
||||
"element": _tmp4,
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (result === void 0) {
|
||||
if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(v)) {
|
||||
let _tmp5: ({}) | undefined;
|
||||
_tmp5 = _.is(v.label, $set) ? {} : void 0;
|
||||
if (_tmp5 !== void 0) {
|
||||
let _tmp6: (Field) | undefined;
|
||||
_tmp6 = toField(v[0]);
|
||||
if (_tmp6 !== void 0) {
|
||||
result = {
|
||||
"_variant": "set",
|
||||
"element": _tmp6,
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (result === void 0) {
|
||||
if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(v)) {
|
||||
let _tmp7: ({}) | undefined;
|
||||
_tmp7 = _.is(v.label, $map) ? {} : void 0;
|
||||
if (_tmp7 !== void 0) {
|
||||
let _tmp8: (Field) | undefined;
|
||||
_tmp8 = toField(v[0]);
|
||||
if (_tmp8 !== void 0) {
|
||||
let _tmp9: (Field) | undefined;
|
||||
_tmp9 = toField(v[1]);
|
||||
if (_tmp9 !== void 0) {
|
||||
result = {
|
||||
"_variant": "map",
|
||||
"key": _tmp8,
|
||||
"value": _tmp9,
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (result === void 0) {
|
||||
if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(v)) {
|
||||
let _tmp10: ({}) | undefined;
|
||||
_tmp10 = _.is(v.label, $ref) ? {} : void 0;
|
||||
if (_tmp10 !== void 0) {
|
||||
let _tmp11: (_i_schema.Ref) | undefined;
|
||||
_tmp11 = _i_schema.toRef<_embedded>(v[0]);
|
||||
if (_tmp11 !== void 0) {
|
||||
result = {
|
||||
"_variant": "ref",
|
||||
"name": _tmp11,
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
if (result === void 0) {
|
||||
let _tmp12: (_i_schema.AtomKind) | undefined;
|
||||
_tmp12 = _i_schema.toAtomKind<_embedded>(v);
|
||||
if (_tmp12 !== void 0) {
|
||||
result = {
|
||||
"_variant": "AtomKind",
|
||||
"value": _tmp12,
|
||||
__as_preserve__() {return fromField(this);},
|
||||
__preserve_on__(e) { e.push(fromField(this)); },
|
||||
__preserve_text_on__(w) { w.push(fromField(this)); }
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
export namespace Field {export const __from_preserve__ = toField;}
|
||||
|
||||
export function fromField<_embedded extends _.Embeddable = _.GenericEmbedded>(_v: Field): _.Value<_embedded> {
|
||||
switch (_v._variant) {
|
||||
case "unit": {return $unit;};
|
||||
case "any": {return $any;};
|
||||
case "embedded": {return $embedded;};
|
||||
case "array": {return _.Record($array, [fromField<_embedded>(_v["element"])]);};
|
||||
case "set": {return _.Record($set, [fromField<_embedded>(_v["element"])]);};
|
||||
case "map": {
|
||||
return _.Record($map, [fromField<_embedded>(_v["key"]), fromField<_embedded>(_v["value"])]);
|
||||
};
|
||||
case "ref": {return _.Record($ref, [_i_schema.fromRef<_embedded>(_v["name"])]);};
|
||||
case "AtomKind": {return _i_schema.fromAtomKind<_embedded>(_v.value);};
|
||||
};
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue