# Cramming scrapscript into msgpack

[^note]:
    This is a work-in-progress! Please email me at
    [hello@taylor.town](mailto:hello@taylor.town) if you have any suggestions,
    criticisms, threats, etc.

[msgpack](https://msgpack.org) is a lovely little serialization format. As a
JSON replacement, it saves bandwidth while preserving native language features
(e.g. tuples, records, objects, dates).

By leveraging msgpack, every [scrapscript](https://scrapscript.org)
program/expression can be crammed into "flat" scraps.

[^note]: When using HTTP, set `Content-Type` to `application/scrap`.

Because flat scraps encode a full programming language, you can send unambiguous
programs/queries across a wire:

```scrapscript
"Joy" |> (name -> ok { name })

-- EXPR       0xc7201f
-- "Joy"      0xa34a6f79
-- name=name  0x81a46e616d65c704786e616d65
-- REC        0xc17b7d
-- OK         0xc10006
-- name       0xc704786e616d65
-- ->         0xc12d3e
-- |>         0xc17c3e
```

...and represent its evaluated result:

```scrapscript
ok { name = "Joy" }

-- EXPR        0xc7101f
-- name="Joy"  0x81a46e616d65a34a6f79
-- REC         0xc17b7d
-- OK          0xc10006
```

This first-class serialization format confers non-obvious benefits:

- a compact format affords a canonical format for hashing
- when computation is cheaper than bandwidth, you can send programs instead of
  sending data
- both code & data can share the same tooling for compression, analysis,
  optimization, etc.
- cheaper to store programs on disk (or KV store) and load into memory
- it's a simple/convenient cross-platform compilation target
- with types, encoders/decoders can be magically inferred/generated/typechecked
  at compile-time
- msgpack is supported [by 50+ languages/environments](https://msgpack.org); all
  msgpack messages are already scraps, and scrap-specific extensions can be
  incrementally implemented in each host language

---

|                             | scrapscript                                      | msgpack                                                                             | extID  |
| --------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------- | ------ |
| [literals](#literals)       | [int](https://scrapscript.org/guide#Numbers)     | [int](https://github.com/msgpack/msgpack/blob/master/spec.md#bool-format-family)    |        |
| [literals](#literals)       | [float](https://scrapscript.org/guide#Numbers)   | [float](https://github.com/msgpack/msgpack/blob/master/spec.md#float-format-family) |        |
| [literals](#literals)       | [text](https://scrapscript.org/guide#Text)       | [str](https://github.com/msgpack/msgpack/blob/master/spec.md#str-format-family)     |        |
| [literals](#literals)       | [bytes](https://scrapscript.org/guide#Bytes)     | [bin](https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family)     |        |
| [literals](#literals)       | [hole](https://scrapscript.org/guide#Hole)       | [nil](https://github.com/msgpack/msgpack/blob/master/spec.md#nil-format)            |        |
| [literals](#literals)       | [bool](https://scrapscript.org/guide#Types)      | [bool](https://github.com/msgpack/msgpack/blob/master/spec.md#bool-format-family)   |        |
| [expressions](#expressions) | expr                                             | [ext](https://github.com/msgpack/msgpack/blob/master/spec.md#ext-format-family)     | `0x1f` |
| [references](#references)   | [var](https://scrapscript.org/guide#Variables)   | [ext](https://github.com/msgpack/msgpack/blob/master/spec.md#ext-format-family)     | `0x76` |
| [references](#references)   | [rock](https://scrapscript.org/guide#Rocks)      | [ext](https://github.com/msgpack/msgpack/blob/master/spec.md#ext-format-family)     | `0x72` |
| [references](#references)   | [hash](https://scrapscript.org/guide#Scrapyards) | [ext](https://github.com/msgpack/msgpack/blob/master/spec.md#ext-format-family)     | `0x24` |
| [structures](#structures)   | [list](https://scrapscript.org/guide#Lists)      | [array](https://github.com/msgpack/msgpack/blob/master/spec.md#array-format-family) |        |
| [structures](#structures)   | [dict](https://scrapscript.org/guide#Rocks)      | \*[map](https://github.com/msgpack/msgpack/blob/master/spec.md#map-format-family)   |        |
| [structures](#structures)   | [tag](https://scrapscript.org/guide#Types)       | \*[map](https://github.com/msgpack/msgpack/blob/master/spec.md#map-format-family)   |        |
| [structures](#structures)   | [record](https://scrapscript.org/guide#Records)  | \*[map](https://github.com/msgpack/msgpack/blob/master/spec.md#map-format-family)   |        |
| [evaluation](#evaluation)   | expr                                             |                                                                                     | `0x1f` |

[^note]:
    Each `extID` classifies its msgpack
    [ext](https://github.com/msgpack/msgpack/blob/master/spec.md#ext-format-family)
    datatype. Each of these bytes mnemonically corresponds to ASCII characters
    (e.g. record is `0x7b` for `{`).

[^note]:
    Versioning remains an open question! Email me at
    [hello@taylor.town](mailto:hello@taylor.town) with your wildest ideas.

## Literals {literals}

| scrapscript          | msgpack                        |
| -------------------- | ------------------------------ |
| `$$bool::false ()`   | `c2`                           |
| `$$bool::true ()`    | `c3`                           |
| `()`                 | `c0`                           |
| `11`                 | `d0` `0b`                      |
| `1234`               | `cd` `04d2`                    |
| `1.2`                | `ca` `3f99999a`                |
| `123456789.1234`     | `cb` `419d6f34547e5c92`        |
| `-&`                 | `cb` `fff0`                    |
| `+&`                 | `cb` `7ff0`                    |
| `;ff`                | `ff`                           |
| `"hi"`               | `a2` `6869`                    |
| `"🐀🐀"`             | `ac` `7ac97bca7ac97bca`        |
| `;;aGVsbG8gd29ybGQ=` | `c4` `0cab` `aGVsbG8gd29ybGQ=` |

## Expressions {expressions}

Flat scrap expressions are written in
[reverse polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation):

| scrapscript    | flatscrap   | msgpack                                         |                              |
| -------------- | ----------- | ----------------------------------------------- | ---------------------------- |
| `1 + 2`        | `1 2 +`     | `c7` `05` `1f` `01` `02` `c1002b`               | ext8 length extID 1 2 +      |
| `(1 + 2) * 3 ` | `1 2 + 3 *` | `c7` `08` `1f` `01` `02` `c1002b` `03` `c1002a` | ext8 length extID 1 2 + 3 \* |
| `1 + 2 * 3 `   | `1 2 3 * +` | `c7` `08` `1f` `01` `02` `03` `c1002a` `c1002b` | ext8 length extID 1 2 3 \* + |

Within the expression, prepend each 2-byte operator with `0xc1`.

Here are many of the normal computer operations you know and love:

|       |        |      |               |
| ----: | -----: | ---: | ------------- |
|   num | `002b` |  `+` | add           |
|   num | `002d` |  `-` | subtract      |
|   num | `002a` |  `*` | multiply      |
|   num | `002f` |  `/` | divide        |
|   num | `005e` |  `^` | pow           |
|   num | `0025` |  `%` | modulo        |
|   num | `2525` | `%%` | remainder     |
|  comp | `003c` |  `<` | lt            |
|  comp | `003e` |  `>` | gt            |
|  comp | `3c3d` | `<=` | lte           |
|  comp | `3e3d` | `>=` | gte           |
|  many | `2b2b` | `++` | concat        |
|  many | `3e2b` | `>+` | prepend       |
|  many | `2b3c` | `+<` | append        |
| bitty | `2626` | `&&` | and           |
| bitty | `7c7c` | `⏐⏐` | or            |
| bitty | `5e5e` | `^^` | xor           |
| nofun | `3d3d` | `==` | equal         |
| nofun | `2f3d` | `/=` | not equal     |
|   fun | `3c7c` | `<⏐` | pipe left     |
|   fun | `7c3e` | `⏐>` | pipe right    |
|   fun | `3c3c` | `<<` | compose left  |
|   fun | `3e3e` | `>>` | compose right |

[^note]:
    Typing is tricky. For example, we want addition to work for floats and
    integers, but we also don't want to introduce ambiguity. We could enforce
    type-specific operators, but that might introduce complexity elsewhere. As
    usual, email [hello@taylor.town](mailto:hello@taylor.town) with thoughts.

Fancier operators are also available:

| scrapscript              | flatscrap             | msgpack                                                                       |
| ------------------------ | --------------------- | ----------------------------------------------------------------------------- |
| `t::a`                   | `t "a" :.`            | `c7081f` `d47674` `a161` `c13a2e`                                             |
| `t::a ()`                | `t "a" () ::`         | `c7081f` `d47674` `a161` `c0` `c13a3a`                                        |
| `rec ~ a`                | `rec "a" ~`           | `c70b1f` `c70476726563` `a161` `c1007e`                                       |
| `a ? a == 1`             | `a "a" 1 == ?`        | `c70c1f` `d47261` `a161` `01` `c13d3d` `c1003f`                               |
| `f 1 2`                  | `f 1 2 apply2`        | `c7081f` `d47666` `01` `02` `c12032`                                          |
| `⏐ 0 -> 0  ⏐ n -> n - 1` | `0 0 -> n n 1 - -> ⏐` | `c7121f` `00` `00` `c12d3e` `d4766e` `d4766e` `01` `c1002d` `c12d3e` `c12d7c` |

| arity |  bytes | char | desc     |
| ----: | -----: | ---: | -------- |
|     2 | `007e` |  `~` | access   |
|     2 | `3a2e` | `:.` | tagger   |
|     3 | `3a3a` | `::` | tag      |
|     2 | `003f` |  `?` | assert   |
|     2 | `2d3e` | `->` | function |
|     2 | `007c` |  `⏐` | case     |
|     2 | `003a` |  `:` | annotate |
|     2 | `003a` |  `:` | annotate |
|     2 | `2031` |      | apply 1  |
|     3 | `2032` |      | apply 2  |
|     4 | `2033` |      | apply 3  |

To keep things tiny, we also cram common expressions into the operator space:

| arity |  bytes |         scrap |
| ----: | -----: | ------------: |
|     1 | `0015` |  `result::no` |
|     1 | `0006` |  `result::ok` |
|     0 | `0030` | `maybe::none` |
|     1 | `0031` | `maybe::some` |
|     2 | `0032` |        `pair` |
|     3 | `0033` |        `trip` |
|     4 | `0034` |        `quad` |
|     2 | `6c6d` |  `$$list-map` |
|     1 | `6e39` |       `$$max` |
|     1 | `6e30` |       `$$min` |
|       |    ... |           ... |

## References {references}

Flat scraps distinguish between
[scope variables](https://scrapscript.org/guide#Variables) (`76`),
[native functions](https://scrapscript.org/guide#Rocks) (`72`), and
[expression hashes](https://scrapscript.org/guide#Scrapyards) (`24`):

| scrapscript      | msgpack                        |                               |
| ---------------- | ------------------------------ | ----------------------------- |
| `x`              | `d4` `76` `78`                 | fixext1 extID "x"             |
| `$$x`            | `d4` `72` `78`                 | fixext1 extID "x"             |
| `$$min`          | `c7` `03` `72` `6d696e`        | ext8 length extID "max"       |
| `$md5;;aG93ZHk=` | `c7` `11` `24` `00` `aG93ZHk=` | ext8 length extID algoID data |

[^note]:
    Hashes point to other flat scraps
    [in a scrapyard](https://scrapscript.org/guide#Scrapyards).

|   ID | algo     |
| ---: | -------- |
| `00` | MD5      |
| `01` | SHA1     |
| `02` | SHA2     |
| `03` | SHA3     |
| `62` | BLAKE    |
| `63` | CityHash |
| `66` | FNV      |
| `6d` | Murmur   |
| `72` | RIPEMD   |
| `73` | SipHash  |
| `78` | xxHash   |

## Structures {structures}

A scrapscript list corresponds to a plain msgpack array:

| scrapscript | msgpack       |                |
| ----------- | ------------- | -------------- |
| `[1,2,3]`   | `93` `010203` | fixarray 1 2 3 |

The other structures are represented as
[maps](https://github.com/msgpack/msgpack/blob/master/spec.md#map-format-family):

| scrapscript                        | msgpack                                    |                         |
| ---------------------------------- | ------------------------------------------ | ----------------------- |
| `[ ;;oWE=';;AQ==, ;;oWI=';;Ag== ]` | `82` `a161` `01` `a162` `02`               | map8 "a" 1 "b" 2        |
| `dict/from-list [ "a"'1, "b"'2 ]`  | `c7161f` `c10064` `82a16101a16202`         | expr `d` `{a=1,b=2}`    |
| `#a c #b d `                       | `c71a1f` `c10023` `82a161d47263a162d47265` | expr `#` `{a=c,b=d}`    |
| `{ a = 1, b = 2 }`                 | `c7161f` `c17b7d` `82a16101a16202`         | expr `{}` `{a=1,b=2}`   |
| `{ a }`                            | `c7151f` `c17b7d` `81a161d47261`           | expr `{}` `{a=a}`       |
| `{ a = 1, ..z }`                   | `c7171f` `c17b7d` `82a16101a0d4727a`       | expr `{}` `{a=1,""=z}`  |
| `{ a = 1, ... }`                   | `c7151f` `c17b7d` `82a16101a0a0`           | expr `{}` `{a=1,""=""}` |

Because maps are ambiguously typed, use special operators to clarify the
intended structure:

| arity |  bytes | char | desc  |
| ----: | -----: | ---: | ----- |
|     1 | `0064` |  `d` | dict  |
|     1 | `7b7d` | `{}` | rec   |
|     1 | `0023` |  `#` | type  |
|     2 | `002e` |  `.` | where |
|     2 | `0021` |  `!` | await |

## Evaluation {evaluation}

Astute observers may have noticed some strange operators like
[where](https://scrapscript.org/guide#Variables) and
[await](https://www.roc-lang.org/tutorial#the-postfix-await-operator):

| scrapscript           | flatscrap           | msgpack                                     |
| --------------------- | ------------------- | ------------------------------------------- |
| `x . x = 1 . y = 2`   | `{"x"=1,"y"=2} x .` | `c7141f` `82a17801a17902` `d47678` `c1002e` |
| `x <- 1 ! y <- 2 ! x` | `{"x"=1,"y"=2} x !` | `c7141f` `82a17801a17902` `d47678` `c10021` |

To build a basic interpreter, push values on a stack and perform operations
in-place. Bind function arguments and environment variables into a scoped map
that can be copied from.

[^note]:
    Blind copies are naive! I'm cooking up some unsafe "hint" operators for
    advanced memory management. Stay tuned.

And someday soon, we'll all be able to do irresponsible stuff like this:

```js
// javascript
const fib = scrap.decode(await fetch("yard.scrap.land/tom/fib"));
console.log(fib(40)); // 102334155
```

```js
// javascript
import fib from "yard.scrap.land/tom/fib";
console.log(fib(40)); // 102334155
```

```go
// golang
import (
  "fmt"
  "yard.scrap.land/tom"
)
func main() {
  fmt.Println(tom.Fib(40)) // 102334155
}
```

```python
# python
async with aiohttp.ClientSession() as sess:
    async with sess.get("yard.scrap.land/tom/fib") as res:
        fib = scrap.decode(await res.read())
        print(fib(40)) # 102334155
```

```js
// javascript
const fib = n => {
  let phi = (1 + Math.sqrt(5)) / 2;
  let asymp = Math.pow(phi, n) / Math.sqrt(5);
  return Math.round(asymp);
};
await fetch("api.example.com", {
  headers: { "Content-Type": "application/scrap" },
  method: "POST",
  body: scrap.encode(fib),
});
```
