forked from Orchid/orchid
Compare commits
31 Commits
networking
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 286040c3ec | |||
| 23180b66e3 | |||
| b44f3c1832 | |||
| 9b4c7fa7d7 | |||
| 0909524dee | |||
| 09cfcb1839 | |||
| cdcca694c5 | |||
| 534f08b45c | |||
| 66e5a71032 | |||
| c461f82de1 | |||
| b9f1bb74d7 | |||
| f38193edcc | |||
| 75b05a2965 | |||
| 9a02c1b3ff | |||
| 4cce216e4e | |||
| 237b40ed2e | |||
| cb111a8d7b | |||
| 48942b3b2c | |||
| 6a3c1d5917 | |||
| 1a7230ce9b | |||
| 32d6237dc5 | |||
| 06debb3636 | |||
| 0b2b05d44e | |||
| 8753d4c751 | |||
| 224c4ecca2 | |||
| 0f89cde246 | |||
| 85d45cf0ef | |||
| d211f3127d | |||
| 4e4dc381ea | |||
| ecf151158d | |||
| 4f989271c5 |
@@ -1,14 +1,24 @@
|
|||||||
[alias]
|
[alias]
|
||||||
xtask = "run --quiet --package xtask --"
|
xtask = "run --quiet --package xtask --"
|
||||||
orcx = "xtask orcx"
|
orcx = "xtask orcx --"
|
||||||
orcxdb = "xtask orcxdb"
|
orcxdb = "xtask orcxdb --"
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
CARGO_WORKSPACE_DIR = { value = "", relative = true }
|
CARGO_WORKSPACE_DIR = { value = "", relative = true }
|
||||||
ORCHID_EXTENSIONS = "target/debug/orchid-std"
|
ORCHID_EXTENSIONS = "target/debug/orchid_std"
|
||||||
|
#ORCHID_EXTENSIONS = "target/debug/orchid-std-piped"
|
||||||
ORCHID_DEFAULT_SYSTEMS = "orchid::std;orchid::macros"
|
ORCHID_DEFAULT_SYSTEMS = "orchid::std;orchid::macros"
|
||||||
ORCHID_LOG_BUFFERS = "true"
|
ORCHID_LOG_BUFFERS = "true"
|
||||||
RUST_BACKTRACE = "1"
|
RUST_BACKTRACE = "1"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
# rustflags = ["-Znext-solver"]
|
# rustflags = ["-Znext-solver"]
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 0
|
||||||
|
debug = 2
|
||||||
|
strip = 'none'
|
||||||
|
debug-assertions = true
|
||||||
|
overflow-checks = true
|
||||||
|
lto = false
|
||||||
|
panic = 'abort'
|
||||||
|
|||||||
13
.github/FUNDING.yml
vendored
13
.github/FUNDING.yml
vendored
@@ -1,13 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
github: lbfalvy
|
|
||||||
patreon: lbfalvy
|
|
||||||
open_collective: # Replace with a single Open Collective username
|
|
||||||
ko_fi: lbfalvy
|
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
||||||
liberapay: # Replace with a single Liberapay username
|
|
||||||
issuehunt: # Replace with a single IssueHunt username
|
|
||||||
otechie: # Replace with a single Otechie username
|
|
||||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
||||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
||||||
29
.github/workflows/rust.yml
vendored
29
.github/workflows/rust.yml
vendored
@@ -1,29 +0,0 @@
|
|||||||
name: Rust
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "master" ]
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
if: ${{ false }} # <- This make sure the workflow is skipped without any alert
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Install rust toolchain
|
|
||||||
run: curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly
|
|
||||||
- name: Build
|
|
||||||
run: $HOME/.cargo/bin/cargo build --verbose
|
|
||||||
- name: Run tests
|
|
||||||
run: $HOME/.cargo/bin/cargo test --verbose
|
|
||||||
- name: Clippy
|
|
||||||
run: cargo clippy
|
|
||||||
- name: Formatting
|
|
||||||
run: cargo fmt +nightly --check
|
|
||||||
1492
Cargo.lock
generated
1492
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,5 +11,8 @@ members = [
|
|||||||
"orchid-api-derive",
|
"orchid-api-derive",
|
||||||
"orchid-api-traits",
|
"orchid-api-traits",
|
||||||
"stdio-perftest",
|
"stdio-perftest",
|
||||||
"xtask", "async-fn-stream",
|
"xtask",
|
||||||
|
"async-fn-stream",
|
||||||
|
"unsync-pipe",
|
||||||
|
"orchid-async-utils",
|
||||||
]
|
]
|
||||||
|
|||||||
6
LICENCE
6
LICENCE
@@ -1,9 +1,11 @@
|
|||||||
|
THIS SOFTWARE IS PROVIDED WITHOUT WARRANTY
|
||||||
|
|
||||||
The code in this repository is free for noncommercial use, including derivative works and inclusion in other software if those are also free for noncommercial use. Commercial use, or inclusion in any derivative works licensed for commercial use is forbidden under this general licence.
|
The code in this repository is free for noncommercial use, including derivative works and inclusion in other software if those are also free for noncommercial use. Commercial use, or inclusion in any derivative works licensed for commercial use is forbidden under this general licence.
|
||||||
|
|
||||||
Identifying marks stored in the repository are restricted for use with an unmodified copy of this software. If you distribute modified versions of this software, you must either replace these identifying marks or modify them in a way that clearly indicates that what you are distributing is a derivative work and not this official vversion. You must also replace any contact information in such a way that your derivative work does not suggest that we may be contacted about issues. Your derivative work may use the original identifying marks and contact information to identify this project as its basis, while emphasizing that the authors of the original project are neither in control of, nor liable for the derivative work.
|
Identifying marks stored in the repository are restricted for use with an unmodified copy of this software. If you distribute modified versions of this software, you must either replace these identifying marks or modify them in a way that clearly indicates that what you are distributing is a derivative work and not this official vversion. You must also replace any contact information in such a way that your derivative work does not suggest that we may be contacted about issues. Your derivative work may use the original identifying marks and contact information to identify this project as its basis, while emphasizing that the authors of the original project are neither in control of, nor liable for the derivative work.
|
||||||
|
|
||||||
Identifying marks include the Orchid logo, the ribbon image above, and the names "Orchid", "Orchidlang" unless they are part of a technical interface.
|
Identifying marks include the Orchid logo, the ribbon image in the readme, and the names "Orchid", "Orchidlang" unless they are part of a technical interface.
|
||||||
|
|
||||||
Contact information includes email addresses, links to the source code and issue tracker.
|
Contact information includes email addresses, links to the source code and issue tracker.
|
||||||
|
|
||||||
Words listed as identifying marks are explicltly not considered as such when they appear in technical interfaces or APIs. For example, shell commands, identifiers within Orchid or Rust code, and names in package registries are not considered as identifying marks.
|
Words listed as identifying marks are explicltly not considered as such when they appear in technical interfaces or APIs. For example, shell commands, identifiers within Orchid or Rust code, and names in package registries are not considered identifying marks.
|
||||||
283
NETWORKING.md
283
NETWORKING.md
@@ -1,283 +0,0 @@
|
|||||||
# Networking in Orchid
|
|
||||||
|
|
||||||
This document explains how to use TCP networking in the Orchid programming language.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Build the project with the nightly Rust toolchain:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rustup run nightly cargo build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Socket Module Overview
|
|
||||||
|
|
||||||
The socket module is available at `std::socket::tcp` and provides functions for TCP client/server communication.
|
|
||||||
|
|
||||||
### Importing
|
|
||||||
|
|
||||||
```orchid
|
|
||||||
import std::socket::tcp::(bind, accept, connect, read, write_all, close)
|
|
||||||
```
|
|
||||||
|
|
||||||
Or import all functions:
|
|
||||||
|
|
||||||
```orchid
|
|
||||||
import std::socket::tcp
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
### Server Functions
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `bind` | `String -> TcpListener` | Create a TCP listener bound to an address |
|
|
||||||
| `accept` | `TcpListener -> TcpStream` | Accept an incoming connection |
|
|
||||||
| `listener_addr` | `TcpListener -> String` | Get the address the listener is bound to |
|
|
||||||
|
|
||||||
### Client Functions
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `connect` | `String -> TcpStream` | Connect to a TCP server |
|
|
||||||
|
|
||||||
### Stream Functions
|
|
||||||
|
|
||||||
| Function | Signature | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `read` | `TcpStream -> Int -> String` | Read up to N bytes as UTF-8 string |
|
|
||||||
| `read_bytes` | `TcpStream -> Int -> String` | Read up to N bytes as raw byte string |
|
|
||||||
| `read_exact` | `TcpStream -> Int -> String` | Read exactly N bytes |
|
|
||||||
| `write` | `TcpStream -> String -> Int` | Write data, returns bytes written |
|
|
||||||
| `write_all` | `TcpStream -> String -> Int` | Write all data |
|
|
||||||
| `flush` | `TcpStream -> Int` | Flush the stream buffer |
|
|
||||||
| `close` | `TcpStream -> Int` | Close the connection |
|
|
||||||
| `peer_addr` | `TcpStream -> String` | Get the remote peer's address |
|
|
||||||
| `local_addr` | `TcpStream -> String` | Get the local address |
|
|
||||||
|
|
||||||
## Quick Start Examples
|
|
||||||
|
|
||||||
### Testing Socket Bind
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rustup run nightly cargo orcx -- exec "std::socket::tcp::bind \"127.0.0.1:8080\""
|
|
||||||
```
|
|
||||||
|
|
||||||
Output:
|
|
||||||
```
|
|
||||||
<TcpListener 127.0.0.1:8080>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing Connection (expect error if no server running)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rustup run nightly cargo orcx -- exec "std::socket::tcp::connect \"127.0.0.1:8080\""
|
|
||||||
```
|
|
||||||
|
|
||||||
Output (if no server):
|
|
||||||
```
|
|
||||||
error: IO error: Connection refused (os error 111): @
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example Scripts
|
|
||||||
|
|
||||||
### TCP Echo Server
|
|
||||||
|
|
||||||
Location: `examples/tcp-echo-server/src/main.orc`
|
|
||||||
|
|
||||||
```orchid
|
|
||||||
import std::socket::tcp::(bind, accept, read, write_all, close)
|
|
||||||
|
|
||||||
const handle_client := \client. do cps {
|
|
||||||
cps data = read client 1024;
|
|
||||||
cps _ = write_all client data;
|
|
||||||
cps _ = close client;
|
|
||||||
cps pass 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accept_loop := \server. do cps {
|
|
||||||
cps client = accept server;
|
|
||||||
cps _ = handle_client client;
|
|
||||||
cps pass $ accept_loop server;
|
|
||||||
}
|
|
||||||
|
|
||||||
const main := do cps {
|
|
||||||
cps server = bind "127.0.0.1:8080";
|
|
||||||
cps pass $ accept_loop server;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Run the echo server:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rustup run nightly cargo orcx -- exec --proj ./examples/tcp-echo-server "src::main::main"
|
|
||||||
```
|
|
||||||
|
|
||||||
### TCP Client
|
|
||||||
|
|
||||||
Location: `examples/tcp-client/src/main.orc`
|
|
||||||
|
|
||||||
```orchid
|
|
||||||
import std::socket::tcp::(connect, read, write_all, close, peer_addr)
|
|
||||||
import system::io::println
|
|
||||||
|
|
||||||
const main := do cps {
|
|
||||||
cps conn = connect "127.0.0.1:8080";
|
|
||||||
cps addr = peer_addr conn;
|
|
||||||
cps println $ "Connected to ${addr}";
|
|
||||||
cps _ = write_all conn "Hello from Orchid!\n";
|
|
||||||
cps response = read conn 1024;
|
|
||||||
cps println $ "Server response: ${response}";
|
|
||||||
cps _ = close conn;
|
|
||||||
cps pass 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Run the client (requires a server running on port 8080):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rustup run nightly cargo orcx -- exec --proj ./examples/tcp-client "src::main::main"
|
|
||||||
```
|
|
||||||
|
|
||||||
### HTTP Server
|
|
||||||
|
|
||||||
Location: `examples/http-server/src/main.orc`
|
|
||||||
|
|
||||||
```orchid
|
|
||||||
import std::socket::tcp::(bind, accept, read, write_all, close, peer_addr, listener_addr)
|
|
||||||
import system::io::println
|
|
||||||
|
|
||||||
const http_response := "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 44\r\nConnection: close\r\n\r\n<html><body><h1>Hello Orchid!</h1></body></html>"
|
|
||||||
|
|
||||||
const handle_client := \client. do cps {
|
|
||||||
cps addr = peer_addr client;
|
|
||||||
cps println $ "Client connected: ${addr}";
|
|
||||||
cps request = read client 4096;
|
|
||||||
cps println $ "Received request from ${addr}";
|
|
||||||
cps _ = write_all client http_response;
|
|
||||||
cps _ = close client;
|
|
||||||
cps println $ "Connection closed: ${addr}";
|
|
||||||
cps pass 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accept_loop := \server. do cps {
|
|
||||||
cps client = accept server;
|
|
||||||
cps _ = handle_client client;
|
|
||||||
cps pass $ accept_loop server;
|
|
||||||
}
|
|
||||||
|
|
||||||
const main := do cps {
|
|
||||||
cps server = bind "127.0.0.1:8080";
|
|
||||||
cps addr = listener_addr server;
|
|
||||||
cps println $ "HTTP Server listening on ${addr}";
|
|
||||||
cps println "Press Ctrl+C to stop";
|
|
||||||
cps pass $ accept_loop server;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Run the HTTP server:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rustup run nightly cargo orcx -- exec --proj ./examples/http-server "src::main::main"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then open http://127.0.0.1:8080 in your browser or test with curl:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
curl http://127.0.0.1:8080
|
|
||||||
```
|
|
||||||
|
|
||||||
## Understanding the CPS Syntax
|
|
||||||
|
|
||||||
Orchid uses Continuation-Passing Style (CPS) for side effects. The `do cps { ... }` block is used for sequential operations:
|
|
||||||
|
|
||||||
- `cps variable = expression;` - Bind the result of an expression to a variable
|
|
||||||
- `cps expression;` - Execute an expression for its side effects
|
|
||||||
- `cps pass value;` - Return a value from the block
|
|
||||||
|
|
||||||
Example breakdown:
|
|
||||||
|
|
||||||
```orchid
|
|
||||||
const main := do cps {
|
|
||||||
cps server = bind "127.0.0.1:8080"; -- Bind socket, store in 'server'
|
|
||||||
cps client = accept server; -- Accept connection, store in 'client'
|
|
||||||
cps data = read client 1024; -- Read data from client
|
|
||||||
cps _ = write_all client data; -- Write data back (ignore result)
|
|
||||||
cps _ = close client; -- Close connection
|
|
||||||
cps pass 0; -- Return 0
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
Socket operations return errors when they fail. Errors are displayed with the IO error message:
|
|
||||||
|
|
||||||
```
|
|
||||||
error: IO error: Connection refused (os error 111): @
|
|
||||||
```
|
|
||||||
|
|
||||||
Common errors:
|
|
||||||
- `Connection refused` - No server listening on the target address
|
|
||||||
- `Address already in use` - Port is already bound by another process
|
|
||||||
- `Connection reset by peer` - Remote end closed the connection unexpectedly
|
|
||||||
|
|
||||||
## Building a Simple Protocol
|
|
||||||
|
|
||||||
Here's an example of a simple request/response protocol:
|
|
||||||
|
|
||||||
```orchid
|
|
||||||
import std::socket::tcp::(bind, accept, read, write_all, close)
|
|
||||||
|
|
||||||
const handle_request := \request.
|
|
||||||
if request == "PING\n" then "PONG\n"
|
|
||||||
else if request == "TIME\n" then "2024-01-01 00:00:00\n"
|
|
||||||
else "UNKNOWN\n"
|
|
||||||
|
|
||||||
const handle_client := \client. do cps {
|
|
||||||
cps request = read client 1024;
|
|
||||||
cps response = pass $ handle_request request;
|
|
||||||
cps _ = write_all client response;
|
|
||||||
cps _ = close client;
|
|
||||||
cps pass 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const server_loop := \server. do cps {
|
|
||||||
cps client = accept server;
|
|
||||||
cps _ = handle_client client;
|
|
||||||
cps pass $ server_loop server;
|
|
||||||
}
|
|
||||||
|
|
||||||
const main := do cps {
|
|
||||||
cps server = bind "127.0.0.1:9000";
|
|
||||||
cps pass $ server_loop server;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Limitations
|
|
||||||
|
|
||||||
- The current implementation handles one client at a time (sequential accept loop)
|
|
||||||
- Sockets are non-serializable (cannot be persisted or transferred across boundaries)
|
|
||||||
- No UDP support (TCP only)
|
|
||||||
- No TLS/SSL support
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Port Already in Use
|
|
||||||
|
|
||||||
If you get "Address already in use", either:
|
|
||||||
1. Wait for the previous process to release the port
|
|
||||||
2. Use a different port
|
|
||||||
3. Kill the process using the port: `lsof -i :8080` then `kill <pid>`
|
|
||||||
|
|
||||||
### Connection Refused
|
|
||||||
|
|
||||||
Ensure the server is running before starting the client.
|
|
||||||
|
|
||||||
### Build Errors
|
|
||||||
|
|
||||||
Make sure to use the nightly toolchain:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
rustup run nightly cargo build
|
|
||||||
```
|
|
||||||
@@ -22,7 +22,7 @@ Namespaces are inspired by Rust modules and ES6. Every file and directory is imp
|
|||||||
The project uses both the stable and nightly rust toolchain. Run the examples with
|
The project uses both the stable and nightly rust toolchain. Run the examples with
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo orcx -- exec --proj ./examples/hello-world "src::main::main"
|
cargo orcx --release exec --proj ./examples/hello-world "src::main::main"
|
||||||
```
|
```
|
||||||
|
|
||||||
you can try modifying the examples, but error reporting for the time being is pretty terrible.
|
you can try modifying the examples, but error reporting for the time being is pretty terrible.
|
||||||
@@ -35,14 +35,14 @@ Orchids and mangrove trees form complex ecosystems; The flowers persuade the tre
|
|||||||
|
|
||||||
All contributions are welcome. For the time being, use the issue tracker to discuss ideas.
|
All contributions are welcome. For the time being, use the issue tracker to discuss ideas.
|
||||||
|
|
||||||
Unless we agree on different terms, by contributing to this software you declare that you have created or otherwise have the right to license your contribution, agree to license it publicly under the general noncommercial licence included in this repository, and grant me (the owner of the project) a permanent, unrestricted license to use, modify, distribute and relicense your contribution under any terms I see fit.
|
Unless we agree on different terms, by contributing to this software you declare that you have created or otherwise have the right to license your contribution, agree to license it publicly under the general noncommercial licence included in this repository, and grant me (the owner of the project) a permanent, unrestricted license to use, modify, distribute and relicense your contribution. You retain ownership of your intellectual property to ensure that the copyleft protections cementing the noncommercial availability of the code are preserved.
|
||||||
|
|
||||||
## About the license
|
## About the license
|
||||||
|
|
||||||
This software is free for noncommercial use. If you would like to use it for commercial purposes, or distribute your derivative work under a license that permits commercial use, contact me for a separate license. These licences are provided on a case-by-case basis with any limitations and compensation we agree on.
|
This software is free for noncommercial use. If you would like to use it for commercial purposes, or distribute your derivative work under a license that permits commercial use, contact me for a separate license. These licences are provided on a case-by-case basis with any limitations and compensation we agree on.
|
||||||
|
|
||||||
I generally appreciate the ethos of open-source software, and particularly the patterns used in copyleft to cement the longevity of the guarantees of the licence. However, I don't think commercial entities fit that ethos, and I think they should be addressed separately rather than attempting to ignore the inherent unfairness towards contributors.
|
I generally appreciate the ethos of free software, and particularly the patterns used in copyleft to cement the guarantees of the licence. However, I don't think commercial entities fit that ethos, and I think they should be addressed separately rather than attempting to ignore the inherent unfairness towards contributors.
|
||||||
|
|
||||||
My intent with the custom license included in this project is to enable the strong guarantees of open-source towards open-source, while leaving commercial users to engage with this project and its possible future ecosystem in a commercial way; if you intend to profit off my work, the barest cash flow should justify shooting me an email and agreeing on a simple temporary profit sharing deal until you figure out your business model, and the cash flow of a full scale business should more than justify dedicated attention to the software you rely on.
|
My intent with the custom license included in this project is to enable the strong guarantees of copyleft towards noncommercial users, while leaving commercial users to engage with this project and its possible future ecosystem in a commercial way; if you intend to profit off my work, the barest cash flow should justify shooting me an email and agreeing on a simple temporary profit sharing deal until you figure out your business model, and the cash flow of a full scale business should more than justify dedicated attention to the software you rely on.
|
||||||
|
|
||||||
The clause about identifying marks is intended to prevent another pitfall of open-source, wherein Linux distros borrow entire codebases, break them, and then distribute the result under the original author's name. If would like to package Orchid, I'd be delighted if you would talk to me about making it official, but if you would rather operate independently, you should present your project as the rogue derivative work that it is rather than borrowing the original project's identity for something its owner has no control over.
|
The clause about identifying marks is intended to prevent another pitfall of open-source, wherein Linux distros borrow entire codebases, break them, and then distribute the result under the original author's name. If would like to package Orchid, I'd be delighted if you would talk to me about making it official, but if you would rather operate independently, you should present your project as the rogue derivative work that it is rather than borrowing the original project's identity for something its owner has no control over.
|
||||||
|
|||||||
@@ -7,4 +7,4 @@ edition = "2024"
|
|||||||
futures = { version = "0.3.31", features = ["std"], default-features = false }
|
futures = { version = "0.3.31", features = ["std"], default-features = false }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test_executors = "0.3.5"
|
test_executors = "0.4.1"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub fn stream<'a, T: 'a>(
|
|||||||
let (send, recv) = mpsc::channel::<T>(1);
|
let (send, recv) = mpsc::channel::<T>(1);
|
||||||
let fut = async { f(StreamCtx(send, PhantomData)).await };
|
let fut = async { f(StreamCtx(send, PhantomData)).await };
|
||||||
// use options to ensure that the stream is driven to exhaustion
|
// use options to ensure that the stream is driven to exhaustion
|
||||||
select_with_strategy(fut.into_stream().map(|()| None), recv.map(|t| Some(t)), left_strat)
|
select_with_strategy(fut.into_stream().map(|()| None), recv.map(Some), left_strat)
|
||||||
.filter_map(async |opt| opt)
|
.filter_map(async |opt| opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
let my_tuple = option::some t[1, 2]
|
let user = r[ "foo" 1, "bar" t[3, 4] ]
|
||||||
let main = tuple::get (option::expect my_tuple "tuple is none") 1
|
let _main = user.bar.1
|
||||||
|
|
||||||
|
let main = "foo" + string::slice "hello" 1 3 + "bar"
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import std::socket::tcp::(bind, accept, read, write_all, close, peer_addr, listener_addr)
|
|
||||||
import system::io::println
|
|
||||||
|
|
||||||
let http_response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 44\r\nConnection: close\r\n\r\n<html><body><h1>Hello Orchid!</h1></body></html>"
|
|
||||||
|
|
||||||
let handle_client = \client. do cps {
|
|
||||||
cps addr = peer_addr client;
|
|
||||||
cps println $ "Client connected: ${addr}";
|
|
||||||
cps request = read client 4096;
|
|
||||||
cps println $ "Received request from ${addr}";
|
|
||||||
cps _ = write_all client http_response;
|
|
||||||
cps _ = close client;
|
|
||||||
cps println $ "Connection closed: ${addr}";
|
|
||||||
cps pass 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let accept_loop = \server. do cps {
|
|
||||||
cps client = accept server;
|
|
||||||
cps _ = handle_client client;
|
|
||||||
cps pass $ accept_loop server;
|
|
||||||
}
|
|
||||||
|
|
||||||
let main = do cps {
|
|
||||||
cps server = bind "127.0.0.1:8080";
|
|
||||||
cps addr = listener_addr server;
|
|
||||||
cps println $ "HTTP Server listening on ${addr}";
|
|
||||||
cps println "Press Ctrl+C to stop";
|
|
||||||
cps pass $ accept_loop server;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import std::socket::tcp::(bind, accept, read, write_all, close, peer_addr, listener_addr)
|
|
||||||
import system::io::println
|
|
||||||
|
|
||||||
cps println $ "Hello Orchid!";
|
|
||||||
|
|
||||||
const http_response := "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 44\r\nConnection: close\r\n\r\n<html><body><h1>Hello Orchid!</h1></body></html>"
|
|
||||||
|
|
||||||
const handle_client := \client. do cps {
|
|
||||||
cps addr = peer_addr client;
|
|
||||||
cps println $ "Client connected: ${addr}";
|
|
||||||
cps request = read client 4096;
|
|
||||||
cps println $ "Received request from ${addr}";
|
|
||||||
cps _ = write_all client http_response;
|
|
||||||
cps _ = close client;
|
|
||||||
cps println $ "Connection closed: ${addr}";
|
|
||||||
cps pass 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accept_loop := \server. do cps {
|
|
||||||
cps client = accept server;
|
|
||||||
cps _ = handle_client client;
|
|
||||||
cps pass $ accept_loop server;
|
|
||||||
}
|
|
||||||
|
|
||||||
const main := do cps {
|
|
||||||
cps server = bind "127.0.0.1:8080";
|
|
||||||
cps addr = listener_addr server;
|
|
||||||
cps println $ "HTTP Server listening on ${addr}";
|
|
||||||
cps println "Press Ctrl+C to stop";
|
|
||||||
cps pass $ accept_loop server;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import std::socket::tcp::(connect, read, write_all, close, peer_addr)
|
|
||||||
import system::io::println
|
|
||||||
|
|
||||||
const main := do cps {
|
|
||||||
cps conn = connect "127.0.0.1:8080";
|
|
||||||
cps addr = peer_addr conn;
|
|
||||||
cps println $ "Connected to ${addr}";
|
|
||||||
cps _ = write_all conn "Hello from Orchid!\n";
|
|
||||||
cps response = read conn 1024;
|
|
||||||
cps println $ "Server response: ${response}";
|
|
||||||
cps _ = close conn;
|
|
||||||
cps pass 0;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import std::socket::tcp::(bind, accept, read, write_all, close)
|
|
||||||
|
|
||||||
const handle_client := \client. do cps {
|
|
||||||
cps data = read client 1024;
|
|
||||||
cps _ = write_all client data;
|
|
||||||
cps _ = close client;
|
|
||||||
cps pass 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accept_loop := \server. do cps {
|
|
||||||
cps client = accept server;
|
|
||||||
cps _ = handle_client client;
|
|
||||||
cps pass $ accept_loop server;
|
|
||||||
}
|
|
||||||
|
|
||||||
const main := do cps {
|
|
||||||
cps server = bind "127.0.0.1:8080";
|
|
||||||
cps pass $ accept_loop server;
|
|
||||||
}
|
|
||||||
21
notes/commands.md
Normal file
21
notes/commands.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Foreword
|
||||||
|
|
||||||
|
Commands exist to allow programs to choose a course of action rather than merely respond to external queries with data. In a sense, commands are data that describes the totality of what a program will do.
|
||||||
|
|
||||||
|
Commands are not equivalent to the instructions of an imperative programming language. If that were the case, they would describe a transition from a previous state to a new state in which additional instructions may be attempted, but commands do not have an "outcome". To the extent that they allow the continuation to be selected, they must encode that within themselves.
|
||||||
|
|
||||||
|
## Are commands unique
|
||||||
|
|
||||||
|
Yes. Since they own the entire program, and since all expressions are younger than all their subexpressions, they cannot be exactly identical to any other subexpression.
|
||||||
|
|
||||||
|
## Are commands exclusive
|
||||||
|
|
||||||
|
Not really. What control flow primitives exist between commands is up to the environment / interpreter. It may make sense to introduce a "parallel" primitive depending on the nature of the commands. In the abstract, we cannot talk about "the current command".
|
||||||
|
|
||||||
|
# Extensions
|
||||||
|
|
||||||
|
The orchid embedder and extension API mean something different by command than any particular programmer or embedder does, and something different still from what Orcx and systems programmers do. The Orchid extension API should not assume any capability that may make an embedder's job unduly difficult.
|
||||||
|
|
||||||
|
## Continuation
|
||||||
|
|
||||||
|
Since commands are expected to be composed into arbitrarily deep TC structures,to avoid a memory leak, commands should not remain passively present in the system; they must be able to express certain outcomes as plain data and return. The most obvious of such outcomes is the single continuation wherein a subexpression evaluating to another command from the same set will eventually run, and it can emulate more complex patterns by continuing with a call to an environment constant which expresses the more complex outcome in terms of its parameters
|
||||||
2
orchid-api-derive/.cargo/config.toml
Normal file
2
orchid-api-derive/.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[profile.dev]
|
||||||
|
panic = 'unwind'
|
||||||
@@ -9,8 +9,8 @@ proc-macro = true
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
quote = "1.0.40"
|
quote = "1.0.42"
|
||||||
syn = { version = "2.0.106" }
|
syn = { version = "2.0.112" }
|
||||||
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
|
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
|
||||||
proc-macro2 = "1.0.101"
|
proc-macro2 = "1.0.104"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ pub fn derive(input: TokenStream) -> TokenStream {
|
|||||||
impl #impl_generics orchid_api_traits::Decode for #name #ty_generics #where_clause {
|
impl #impl_generics orchid_api_traits::Decode for #name #ty_generics #where_clause {
|
||||||
async fn decode<R: orchid_api_traits::AsyncRead + ?Sized>(
|
async fn decode<R: orchid_api_traits::AsyncRead + ?Sized>(
|
||||||
mut read: std::pin::Pin<&mut R>
|
mut read: std::pin::Pin<&mut R>
|
||||||
) -> Self {
|
) -> std::io::Result<Self> {
|
||||||
#decode
|
Ok(#decode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -30,7 +30,7 @@ fn decode_fields(fields: &syn::Fields) -> pm2::TokenStream {
|
|||||||
let syn::Field { ty, ident, .. } = &f;
|
let syn::Field { ty, ident, .. } = &f;
|
||||||
quote! {
|
quote! {
|
||||||
#ident : (Box::pin(< #ty as orchid_api_traits::Decode>::decode(read.as_mut()))
|
#ident : (Box::pin(< #ty as orchid_api_traits::Decode>::decode(read.as_mut()))
|
||||||
as std::pin::Pin<Box<dyn std::future::Future<Output = _>>>).await
|
as std::pin::Pin<Box<dyn std::future::Future<Output = std::io::Result<_>>>>).await?
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
quote! { { #( #exprs, )* } }
|
quote! { { #( #exprs, )* } }
|
||||||
@@ -40,7 +40,7 @@ fn decode_fields(fields: &syn::Fields) -> pm2::TokenStream {
|
|||||||
let ty = &field.ty;
|
let ty = &field.ty;
|
||||||
quote! {
|
quote! {
|
||||||
(Box::pin(< #ty as orchid_api_traits::Decode>::decode(read.as_mut()))
|
(Box::pin(< #ty as orchid_api_traits::Decode>::decode(read.as_mut()))
|
||||||
as std::pin::Pin<Box<dyn std::future::Future<Output = _>>>).await,
|
as std::pin::Pin<Box<dyn std::future::Future<Output = std::io::Result<_>>>>).await?,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
quote! { ( #( #exprs )* ) }
|
quote! { ( #( #exprs )* ) }
|
||||||
@@ -62,7 +62,7 @@ fn decode_body(data: &syn::Data) -> proc_macro2::TokenStream {
|
|||||||
quote! { #id => Self::#ident #fields, }
|
quote! { #id => Self::#ident #fields, }
|
||||||
});
|
});
|
||||||
quote! {
|
quote! {
|
||||||
match <u8 as orchid_api_traits::Decode>::decode(read.as_mut()).await {
|
match <u8 as orchid_api_traits::Decode>::decode(read.as_mut()).await? {
|
||||||
#(#opts)*
|
#(#opts)*
|
||||||
x => panic!("Unrecognized enum kind {x}")
|
x => panic!("Unrecognized enum kind {x}")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ pub fn derive(input: TokenStream) -> TokenStream {
|
|||||||
async fn encode<W: orchid_api_traits::AsyncWrite + ?Sized>(
|
async fn encode<W: orchid_api_traits::AsyncWrite + ?Sized>(
|
||||||
&self,
|
&self,
|
||||||
mut write: std::pin::Pin<&mut W>
|
mut write: std::pin::Pin<&mut W>
|
||||||
) {
|
) -> std::io::Result<()> {
|
||||||
#encode
|
#encode;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -43,7 +44,7 @@ fn encode_body(data: &syn::Data) -> Option<pm2::TokenStream> {
|
|||||||
quote! {
|
quote! {
|
||||||
Self::#ident #dest => {
|
Self::#ident #dest => {
|
||||||
(Box::pin((#i as u8).encode(write.as_mut()))
|
(Box::pin((#i as u8).encode(write.as_mut()))
|
||||||
as std::pin::Pin<Box<dyn std::future::Future<Output = _>>>).await;
|
as std::pin::Pin<Box<dyn std::future::Future<Output = std::io::Result<()>>>>).await?;
|
||||||
#body
|
#body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,7 +62,7 @@ fn encode_body(data: &syn::Data) -> Option<pm2::TokenStream> {
|
|||||||
fn encode_names<T: ToTokens>(names: impl Iterator<Item = T>) -> pm2::TokenStream {
|
fn encode_names<T: ToTokens>(names: impl Iterator<Item = T>) -> pm2::TokenStream {
|
||||||
quote! { #(
|
quote! { #(
|
||||||
(Box::pin(#names .encode(write.as_mut()))
|
(Box::pin(#names .encode(write.as_mut()))
|
||||||
as std::pin::Pin<Box<dyn std::future::Future<Output = _>>>).await;
|
as std::pin::Pin<Box<dyn std::future::Future<Output = std::io::Result<()>>>>).await?;
|
||||||
)* }
|
)* }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,3 @@ fn get_ancestry(input: &DeriveInput) -> Option<Vec<pm2::TokenStream>> {
|
|||||||
fn is_extendable(input: &DeriveInput) -> bool {
|
fn is_extendable(input: &DeriveInput) -> bool {
|
||||||
input.attrs.iter().any(|a| a.path().get_ident().is_some_and(|i| *i == "extendable"))
|
input.attrs.iter().any(|a| a.path().get_ident().is_some_and(|i| *i == "extendable"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_wtf() { eprintln!("{}", gen_casts(&[quote!(ExtHostReq)], "e!(BogusReq))) }
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ edition = "2024"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
|
chrono = "0.4.43"
|
||||||
futures = { version = "0.3.31", features = ["std"], default-features = false }
|
futures = { version = "0.3.31", features = ["std"], default-features = false }
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
never = "0.1.0"
|
never = "0.1.0"
|
||||||
ordered-float = "5.0.0"
|
ordered-float = "5.1.0"
|
||||||
|
|||||||
@@ -1,33 +1,45 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
use std::io;
|
||||||
use std::num::NonZero;
|
use std::num::NonZero;
|
||||||
use std::ops::{Range, RangeInclusive};
|
use std::ops::{Range, RangeInclusive};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use async_fn_stream::stream;
|
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, StreamExt};
|
|
||||||
use never::Never;
|
use never::Never;
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
use crate::encode_enum;
|
use crate::{decode_err, decode_err_for, encode_enum, spin_on};
|
||||||
|
|
||||||
pub trait Decode: 'static {
|
pub trait Decode: 'static + Sized {
|
||||||
/// Decode an instance from the beginning of the buffer. Return the decoded
|
/// Decode an instance from the beginning of the buffer. Return the decoded
|
||||||
/// data and the remaining buffer.
|
/// data and the remaining buffer.
|
||||||
fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> impl Future<Output = Self> + '_;
|
fn decode<R: AsyncRead + ?Sized>(
|
||||||
|
read: Pin<&mut R>,
|
||||||
|
) -> impl Future<Output = io::Result<Self>> + '_;
|
||||||
|
fn decode_slice(slc: &mut &[u8]) -> Self {
|
||||||
|
spin_on(Self::decode(Pin::new(slc) as Pin<&mut _>)).expect("Decode from slice cannot fail")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub trait Encode {
|
pub trait Encode {
|
||||||
/// Append an instance of the struct to the buffer
|
/// Append an instance of the struct to the buffer
|
||||||
fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> impl Future<Output = ()>;
|
fn encode<W: AsyncWrite + ?Sized>(
|
||||||
|
&self,
|
||||||
|
write: Pin<&mut W>,
|
||||||
|
) -> impl Future<Output = io::Result<()>>;
|
||||||
|
fn encode_vec(&self, vec: &mut Vec<u8>) {
|
||||||
|
spin_on(self.encode(Pin::new(vec) as Pin<&mut _>)).expect("Encode to vector cannot fail")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub trait Coding: Encode + Decode + Clone {
|
pub trait Coding: Encode + Decode + Clone {
|
||||||
fn get_decoder<T: 'static, F: Future<Output = T> + 'static>(
|
fn get_decoder<T: 'static>(
|
||||||
map: impl Fn(Self) -> F + Clone + 'static,
|
map: impl AsyncFn(Self) -> T + Clone + 'static,
|
||||||
) -> impl AsyncFn(Pin<&mut dyn AsyncRead>) -> T {
|
) -> impl AsyncFn(Pin<&mut dyn AsyncRead>) -> io::Result<T> {
|
||||||
async move |r| map(Self::decode(r).await).await
|
async move |r| Ok(map(Self::decode(r).await?).await)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Encode + Decode + Clone> Coding for T {}
|
impl<T: Encode + Decode + Clone> Coding for T {}
|
||||||
@@ -35,15 +47,15 @@ impl<T: Encode + Decode + Clone> Coding for T {}
|
|||||||
macro_rules! num_impl {
|
macro_rules! num_impl {
|
||||||
($number:ty) => {
|
($number:ty) => {
|
||||||
impl Decode for $number {
|
impl Decode for $number {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
let mut bytes = [0u8; (<$number>::BITS / 8) as usize];
|
let mut bytes = [0u8; (<$number>::BITS / 8) as usize];
|
||||||
read.read_exact(&mut bytes).await.unwrap();
|
read.read_exact(&mut bytes).await?;
|
||||||
<$number>::from_be_bytes(bytes)
|
Ok(<$number>::from_be_bytes(bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Encode for $number {
|
impl Encode for $number {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
write.write_all(&self.to_be_bytes()).await.expect("Could not write number")
|
write.write_all(&self.to_be_bytes()).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -62,12 +74,12 @@ num_impl!(i8);
|
|||||||
macro_rules! nonzero_impl {
|
macro_rules! nonzero_impl {
|
||||||
($name:ty) => {
|
($name:ty) => {
|
||||||
impl Decode for NonZero<$name> {
|
impl Decode for NonZero<$name> {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
Self::new(<$name as Decode>::decode(read).await).unwrap()
|
Self::new(<$name as Decode>::decode(read).await?).ok_or_else(decode_err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Encode for NonZero<$name> {
|
impl Encode for NonZero<$name> {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
|
||||||
self.get().encode(write).await
|
self.get().encode(write).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,22 +98,22 @@ nonzero_impl!(i64);
|
|||||||
nonzero_impl!(i128);
|
nonzero_impl!(i128);
|
||||||
|
|
||||||
impl<T: Encode + ?Sized> Encode for &T {
|
impl<T: Encode + ?Sized> Encode for &T {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
|
||||||
(**self).encode(write).await
|
(**self).encode(write).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
macro_rules! float_impl {
|
macro_rules! float_impl {
|
||||||
($t:ty, $size:expr) => {
|
($t:ty, $size:expr) => {
|
||||||
impl Decode for NotNan<$t> {
|
impl Decode for NotNan<$t> {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
let mut bytes = [0u8; $size];
|
let mut bytes = [0u8; $size];
|
||||||
read.read_exact(&mut bytes).await.unwrap();
|
read.read_exact(&mut bytes).await?;
|
||||||
NotNan::new(<$t>::from_be_bytes(bytes)).expect("Float was NaN")
|
NotNan::new(<$t>::from_be_bytes(bytes)).map_err(|_| decode_err())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Encode for NotNan<$t> {
|
impl Encode for NotNan<$t> {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
write.write_all(&self.as_ref().to_be_bytes()).await.expect("Could not write number")
|
write.write_all(&self.as_ref().to_be_bytes()).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -111,78 +123,77 @@ float_impl!(f64, 8);
|
|||||||
float_impl!(f32, 4);
|
float_impl!(f32, 4);
|
||||||
|
|
||||||
impl Decode for String {
|
impl Decode for String {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
let len = u64::decode(read.as_mut()).await.try_into().unwrap();
|
let len: usize = u64::decode(read.as_mut()).await?.try_into().map_err(decode_err_for)?;
|
||||||
let mut data = vec![0u8; len];
|
let mut data = vec![0u8; len];
|
||||||
read.read_exact(&mut data).await.unwrap();
|
read.read_exact(&mut data).await?;
|
||||||
std::str::from_utf8(&data).expect("String invalid UTF-8").to_owned()
|
Ok(std::str::from_utf8(&data).map_err(decode_err_for)?.to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Encode for String {
|
impl Encode for String {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await;
|
u64::try_from(self.len()).map_err(decode_err_for)?.encode(write.as_mut()).await?;
|
||||||
write.write_all(self.as_bytes()).await.unwrap()
|
write.write_all(self.as_bytes()).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Encode for str {
|
impl Encode for str {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await;
|
u64::try_from(self.len()).map_err(decode_err_for)?.encode(write.as_mut()).await?;
|
||||||
write.write_all(self.as_bytes()).await.unwrap()
|
write.write_all(self.as_bytes()).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Decode> Decode for Vec<T> {
|
impl<T: Decode> Decode for Vec<T> {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
let len = u64::decode(read.as_mut()).await.try_into().unwrap();
|
let len = u64::decode(read.as_mut()).await?;
|
||||||
stream(async |mut cx| {
|
let mut values = Vec::with_capacity(len.try_into().map_err(decode_err_for)?);
|
||||||
for _ in 0..len {
|
for _ in 0..len {
|
||||||
cx.emit(T::decode(read.as_mut()).await).await
|
values.push(T::decode(read.as_mut()).await?);
|
||||||
}
|
}
|
||||||
})
|
Ok(values)
|
||||||
.collect()
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Encode> Encode for Vec<T> {
|
impl<T: Encode> Encode for Vec<T> {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
|
||||||
self.as_slice().encode(write).await
|
self.as_slice().encode(write).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Encode> Encode for [T] {
|
impl<T: Encode> Encode for [T] {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await;
|
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await?;
|
||||||
for t in self.iter() {
|
for t in self.iter() {
|
||||||
t.encode(write.as_mut()).await
|
t.encode(write.as_mut()).await?
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Decode> Decode for Option<T> {
|
impl<T: Decode> Decode for Option<T> {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
match u8::decode(read.as_mut()).await {
|
Ok(match bool::decode(read.as_mut()).await? {
|
||||||
0 => None,
|
false => None,
|
||||||
1 => Some(T::decode(read).await),
|
true => Some(T::decode(read).await?),
|
||||||
x => panic!("{x} is not a valid option value"),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Encode> Encode for Option<T> {
|
impl<T: Encode> Encode for Option<T> {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
let t = if let Some(t) = self { t } else { return 0u8.encode(write.as_mut()).await };
|
self.is_some().encode(write.as_mut()).await?;
|
||||||
1u8.encode(write.as_mut()).await;
|
if let Some(t) = self {
|
||||||
t.encode(write).await;
|
t.encode(write).await?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Decode, E: Decode> Decode for Result<T, E> {
|
impl<T: Decode, E: Decode> Decode for Result<T, E> {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
match u8::decode(read.as_mut()).await {
|
Ok(match bool::decode(read.as_mut()).await? {
|
||||||
0 => Self::Ok(T::decode(read).await),
|
false => Self::Ok(T::decode(read).await?),
|
||||||
1 => Self::Err(E::decode(read).await),
|
true => Self::Err(E::decode(read).await?),
|
||||||
x => panic!("Invalid Result tag {x}"),
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Encode, E: Encode> Encode for Result<T, E> {
|
impl<T: Encode, E: Encode> Encode for Result<T, E> {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Ok(t) => encode_enum(write, 0, |w| t.encode(w)).await,
|
Ok(t) => encode_enum(write, 0, |w| t.encode(w)).await,
|
||||||
Err(e) => encode_enum(write, 1, |w| e.encode(w)).await,
|
Err(e) => encode_enum(write, 1, |w| e.encode(w)).await,
|
||||||
@@ -190,36 +201,37 @@ impl<T: Encode, E: Encode> Encode for Result<T, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<K: Decode + Eq + Hash, V: Decode> Decode for HashMap<K, V> {
|
impl<K: Decode + Eq + Hash, V: Decode> Decode for HashMap<K, V> {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
let len = u64::decode(read.as_mut()).await.try_into().unwrap();
|
let len = u64::decode(read.as_mut()).await?;
|
||||||
stream(async |mut cx| {
|
let mut map = HashMap::with_capacity(len.try_into().map_err(decode_err_for)?);
|
||||||
for _ in 0..len {
|
for _ in 0..len {
|
||||||
cx.emit(<(K, V)>::decode(read.as_mut()).await).await
|
map.insert(K::decode(read.as_mut()).await?, V::decode(read.as_mut()).await?);
|
||||||
}
|
}
|
||||||
})
|
Ok(map)
|
||||||
.collect()
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<K: Encode + Eq + Hash, V: Encode> Encode for HashMap<K, V> {
|
impl<K: Encode + Eq + Hash, V: Encode> Encode for HashMap<K, V> {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await;
|
u64::try_from(self.len()).unwrap().encode(write.as_mut()).await?;
|
||||||
for pair in self.iter() {
|
for (key, value) in self.iter() {
|
||||||
pair.encode(write.as_mut()).await
|
key.encode(write.as_mut()).await?;
|
||||||
|
value.encode(write.as_mut()).await?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
macro_rules! tuple {
|
macro_rules! tuple {
|
||||||
(($($t:ident)*) ($($T:ident)*)) => {
|
(($($t:ident)*) ($($T:ident)*)) => {
|
||||||
impl<$($T: Decode),*> Decode for ($($T,)*) {
|
impl<$($T: Decode),*> Decode for ($($T,)*) {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
($($T::decode(read.as_mut()).await,)*)
|
Ok(($($T::decode(read.as_mut()).await?,)*))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<$($T: Encode),*> Encode for ($($T,)*) {
|
impl<$($T: Encode),*> Encode for ($($T,)*) {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
let ($($t,)*) = self;
|
let ($($t,)*) = self;
|
||||||
$( $t.encode(write.as_mut()).await; )*
|
$( $t.encode(write.as_mut()).await?; )*
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -243,63 +255,67 @@ tuple!((t u v x y z a b c d e f g h i) (T U V X Y Z A B C D E F G H I));
|
|||||||
tuple!((t u v x y z a b c d e f g h i j) (T U V X Y Z A B C D E F G H I J)); // 16
|
tuple!((t u v x y z a b c d e f g h i j) (T U V X Y Z A B C D E F G H I J)); // 16
|
||||||
|
|
||||||
impl Decode for () {
|
impl Decode for () {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(_: Pin<&mut R>) -> Self {}
|
async fn decode<R: AsyncRead + ?Sized>(_: Pin<&mut R>) -> io::Result<Self> { Ok(()) }
|
||||||
}
|
}
|
||||||
impl Encode for () {
|
impl Encode for () {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, _: Pin<&mut W>) {}
|
async fn encode<W: AsyncWrite + ?Sized>(&self, _: Pin<&mut W>) -> io::Result<()> { Ok(()) }
|
||||||
}
|
}
|
||||||
impl Decode for Never {
|
impl Decode for Never {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(_: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(_: Pin<&mut R>) -> io::Result<Self> {
|
||||||
unreachable!("A value of Never cannot exist so it can't have been serialized");
|
unreachable!("A value of Never cannot exist so it can't have been serialized");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Encode for Never {
|
impl Encode for Never {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, _: Pin<&mut W>) { match *self {} }
|
async fn encode<W: AsyncWrite + ?Sized>(&self, _: Pin<&mut W>) -> io::Result<()> {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl Decode for bool {
|
impl Decode for bool {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
let mut buf = [0];
|
let mut buf = [0];
|
||||||
read.read_exact(&mut buf).await.unwrap();
|
read.read_exact(&mut buf).await?;
|
||||||
buf[0] != 0
|
Ok(buf[0] != 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Encode for bool {
|
impl Encode for bool {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
write.write_all(&[if *self { 0xffu8 } else { 0u8 }]).await.unwrap()
|
write.write_all(&[if *self { 0xffu8 } else { 0u8 }]).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Decode, const N: usize> Decode for [T; N] {
|
impl<T: Decode, const N: usize> Decode for [T; N] {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
let v = stream(async |mut cx| {
|
let mut v = Vec::with_capacity(N);
|
||||||
for _ in 0..N {
|
for _ in 0..N {
|
||||||
cx.emit(T::decode(read.as_mut()).await).await
|
v.push(T::decode(read.as_mut()).await?);
|
||||||
|
}
|
||||||
|
match v.try_into() {
|
||||||
|
Err(_) => unreachable!("The length of this stream is statically known"),
|
||||||
|
Ok(arr) => Ok(arr),
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.await;
|
|
||||||
v.try_into().unwrap_or_else(|_| unreachable!("The length of this stream is statically known"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Encode, const N: usize> Encode for [T; N] {
|
impl<T: Encode, const N: usize> Encode for [T; N] {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
for t in self.iter() {
|
for t in self.iter() {
|
||||||
t.encode(write.as_mut()).await
|
t.encode(write.as_mut()).await?
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! two_end_range {
|
macro_rules! two_end_range {
|
||||||
($this:ident, $name:tt, $op:tt, $start:expr, $end:expr) => {
|
($this:ident, $name:tt, $op:tt, $start:expr, $end:expr) => {
|
||||||
impl<T: Decode> Decode for $name<T> {
|
impl<T: Decode> Decode for $name<T> {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
T::decode(read.as_mut()).await $op T::decode(read).await
|
Ok(T::decode(read.as_mut()).await? $op T::decode(read).await?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Encode> Encode for $name<T> {
|
impl<T: Encode> Encode for $name<T> {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
let $this = self;
|
let $this = self;
|
||||||
($start).encode(write.as_mut()).await;
|
($start).encode(write.as_mut()).await?;
|
||||||
($end).encode(write).await;
|
($end).encode(write).await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,12 +327,12 @@ two_end_range!(x, RangeInclusive, ..=, x.start(), x.end());
|
|||||||
macro_rules! smart_ptr {
|
macro_rules! smart_ptr {
|
||||||
($name:tt) => {
|
($name:tt) => {
|
||||||
impl<T: Decode> Decode for $name<T> {
|
impl<T: Decode> Decode for $name<T> {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
$name::new(T::decode(read).await)
|
Ok($name::new(T::decode(read).await?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T: Encode> Encode for $name<T> {
|
impl<T: Encode> Encode for $name<T> {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
|
||||||
(**self).encode(write).await
|
(**self).encode(write).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,12 +344,45 @@ smart_ptr!(Rc);
|
|||||||
smart_ptr!(Box);
|
smart_ptr!(Box);
|
||||||
|
|
||||||
impl Decode for char {
|
impl Decode for char {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
char::from_u32(u32::decode(read).await).unwrap()
|
char::from_u32(u32::decode(read).await?).ok_or_else(decode_err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Encode for char {
|
impl Encode for char {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
|
||||||
(*self as u32).encode(write).await
|
(*self as u32).encode(write).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Decode for Duration {
|
||||||
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
|
Ok(Self::new(u64::decode(read.as_mut()).await?, u32::decode(read).await?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Encode for Duration {
|
||||||
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
|
self.as_secs().encode(write.as_mut()).await?;
|
||||||
|
self.subsec_nanos().encode(write).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Decode for chrono::TimeDelta {
|
||||||
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
|
Ok(Self::new(i64::decode(read.as_mut()).await?, u32::decode(read).await?).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Encode for chrono::TimeDelta {
|
||||||
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
|
self.num_seconds().encode(write.as_mut()).await?;
|
||||||
|
self.subsec_nanos().encode(write).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Decode for chrono::DateTime<chrono::Utc> {
|
||||||
|
async fn decode<R: AsyncRead + ?Sized>(read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
|
Ok(Self::from_timestamp_micros(i64::decode(read).await?).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Encode for chrono::DateTime<chrono::Utc> {
|
||||||
|
async fn encode<W: AsyncWrite + ?Sized>(&self, write: Pin<&mut W>) -> io::Result<()> {
|
||||||
|
self.timestamp_micros().encode(write).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
use std::future::Future;
|
use std::error::Error;
|
||||||
use std::pin::Pin;
|
use std::io;
|
||||||
|
use std::pin::{Pin, pin};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::task::{Context, Poll, Wake};
|
||||||
|
|
||||||
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
use futures::{AsyncRead, AsyncReadExt, AsyncWrite};
|
||||||
use itertools::{Chunk, Itertools};
|
use itertools::{Chunk, Itertools};
|
||||||
|
|
||||||
use crate::Encode;
|
use crate::Encode;
|
||||||
|
|
||||||
pub async fn encode_enum<'a, W: AsyncWrite + ?Sized, F: Future<Output = ()>>(
|
pub async fn encode_enum<'a, W: AsyncWrite + ?Sized>(
|
||||||
mut write: Pin<&'a mut W>,
|
mut write: Pin<&'a mut W>,
|
||||||
id: u8,
|
id: u8,
|
||||||
f: impl FnOnce(Pin<&'a mut W>) -> F,
|
f: impl AsyncFnOnce(Pin<&'a mut W>) -> io::Result<()>,
|
||||||
) {
|
) -> io::Result<()> {
|
||||||
id.encode(write.as_mut()).await;
|
id.encode(write.as_mut()).await?;
|
||||||
f(write).await
|
f(write).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn write_exact<W: AsyncWrite + ?Sized>(mut write: Pin<&mut W>, bytes: &'static [u8]) {
|
|
||||||
write.write_all(bytes).await.expect("Failed to write exact bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_bytes(b: &[u8]) -> String {
|
pub fn print_bytes(b: &[u8]) -> String {
|
||||||
(b.iter().map(|b| format!("{b:02x}")))
|
(b.iter().map(|b| format!("{b:02x}")))
|
||||||
.chunks(4)
|
.chunks(4)
|
||||||
@@ -27,16 +27,52 @@ pub fn print_bytes(b: &[u8]) -> String {
|
|||||||
.join(" ")
|
.join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_exact<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>, bytes: &'static [u8]) {
|
pub async fn read_exact<R: AsyncRead + ?Sized>(
|
||||||
|
mut read: Pin<&mut R>,
|
||||||
|
bytes: &'static [u8],
|
||||||
|
) -> io::Result<()> {
|
||||||
let mut data = vec![0u8; bytes.len()];
|
let mut data = vec![0u8; bytes.len()];
|
||||||
read.read_exact(&mut data).await.expect("Failed to read bytes");
|
read.read_exact(&mut data).await?;
|
||||||
if data != bytes {
|
if data == bytes {
|
||||||
panic!("Wrong bytes!\nExpected: {}\nFound: {}", print_bytes(bytes), print_bytes(&data));
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let msg =
|
||||||
|
format!("Wrong bytes!\nExpected: {}\nFound: {}", print_bytes(bytes), print_bytes(&data));
|
||||||
|
Err(io::Error::new(io::ErrorKind::InvalidData, msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn enc_vec(enc: &impl Encode) -> Vec<u8> {
|
pub fn enc_vec(enc: &impl Encode) -> Vec<u8> {
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
enc.encode(Pin::new(&mut vec)).await;
|
enc.encode_vec(&mut vec);
|
||||||
vec
|
vec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Raises a bool flag when called
|
||||||
|
struct FlagWaker(AtomicBool);
|
||||||
|
impl Wake for FlagWaker {
|
||||||
|
fn wake(self: Arc<Self>) { self.0.store(true, Ordering::Relaxed) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spin_on<F: Future>(fut: F) -> F::Output {
|
||||||
|
let flag = AtomicBool::new(false);
|
||||||
|
let flag_waker = Arc::new(FlagWaker(flag));
|
||||||
|
let mut future = pin!(fut);
|
||||||
|
loop {
|
||||||
|
let waker = flag_waker.clone().into();
|
||||||
|
let mut ctx = Context::from_waker(&waker);
|
||||||
|
match future.as_mut().poll(&mut ctx) {
|
||||||
|
// ideally the future should return synchronously
|
||||||
|
Poll::Ready(res) => break res,
|
||||||
|
// poorly written futures may yield and immediately wake
|
||||||
|
Poll::Pending if flag_waker.0.load(Ordering::Relaxed) => (),
|
||||||
|
// there is no external event to wait for, this has to be a deadlock
|
||||||
|
Poll::Pending => panic!("Future inside spin_on cannot block"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decode_err() -> io::Error { io::Error::new(io::ErrorKind::InvalidData, "Unexpected zero") }
|
||||||
|
pub fn decode_err_for(e: impl Error) -> io::Error {
|
||||||
|
io::Error::new(io::ErrorKind::InvalidData, e.to_string())
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ impl TLBool for TLTrue {}
|
|||||||
pub struct TLFalse;
|
pub struct TLFalse;
|
||||||
impl TLBool for TLFalse {}
|
impl TLBool for TLFalse {}
|
||||||
|
|
||||||
/// A type that implements [Hierarchy]. Used to select implementations of traits
|
/// A type that implements [Extends] or a root. Used to select implementations
|
||||||
/// on the hierarchy
|
/// of traits on the hierarchy
|
||||||
pub trait InHierarchy: Clone {
|
pub trait InHierarchy: Clone {
|
||||||
/// Indicates that this hierarchy element is a leaf. Leaves can never have
|
/// Indicates that this hierarchy element is a leaf. Leaves can never have
|
||||||
/// children
|
/// children
|
||||||
@@ -29,25 +29,21 @@ pub trait Extends: InHierarchy<IsRoot = TLFalse> + Into<Self::Parent> {
|
|||||||
pub trait UnderRootImpl<IsRoot: TLBool>: Sized {
|
pub trait UnderRootImpl<IsRoot: TLBool>: Sized {
|
||||||
type __Root: UnderRoot<IsRoot = TLTrue, Root = Self::__Root>;
|
type __Root: UnderRoot<IsRoot = TLTrue, Root = Self::__Root>;
|
||||||
fn __into_root(self) -> Self::__Root;
|
fn __into_root(self) -> Self::__Root;
|
||||||
fn __try_from_root(root: Self::__Root) -> Result<Self, Self::__Root>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait UnderRoot: InHierarchy {
|
pub trait UnderRoot: InHierarchy {
|
||||||
type Root: UnderRoot<IsRoot = TLTrue, Root = Self::Root>;
|
type Root: UnderRoot<IsRoot = TLTrue, Root = Self::Root>;
|
||||||
fn into_root(self) -> Self::Root;
|
fn into_root(self) -> Self::Root;
|
||||||
fn try_from_root(root: Self::Root) -> Result<Self, Self::Root>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InHierarchy + UnderRootImpl<T::IsRoot>> UnderRoot for T {
|
impl<T: InHierarchy + UnderRootImpl<T::IsRoot>> UnderRoot for T {
|
||||||
type Root = <Self as UnderRootImpl<<Self as InHierarchy>::IsRoot>>::__Root;
|
type Root = <Self as UnderRootImpl<<Self as InHierarchy>::IsRoot>>::__Root;
|
||||||
fn into_root(self) -> Self::Root { self.__into_root() }
|
fn into_root(self) -> Self::Root { self.__into_root() }
|
||||||
fn try_from_root(root: Self::Root) -> Result<Self, Self::Root> { Self::__try_from_root(root) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InHierarchy<IsRoot = TLTrue>> UnderRootImpl<TLTrue> for T {
|
impl<T: InHierarchy<IsRoot = TLTrue>> UnderRootImpl<TLTrue> for T {
|
||||||
type __Root = Self;
|
type __Root = Self;
|
||||||
fn __into_root(self) -> Self::__Root { self }
|
fn __into_root(self) -> Self::__Root { self }
|
||||||
fn __try_from_root(root: Self::__Root) -> Result<Self, Self::__Root> { Ok(root) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InHierarchy<IsRoot = TLFalse> + Extends> UnderRootImpl<TLFalse> for T {
|
impl<T: InHierarchy<IsRoot = TLFalse> + Extends> UnderRootImpl<TLFalse> for T {
|
||||||
@@ -57,8 +53,4 @@ impl<T: InHierarchy<IsRoot = TLFalse> + Extends> UnderRootImpl<TLFalse> for T {
|
|||||||
fn __into_root(self) -> Self::__Root {
|
fn __into_root(self) -> Self::__Root {
|
||||||
<Self as Into<<Self as Extends>::Parent>>::into(self).into_root()
|
<Self as Into<<Self as Extends>::Parent>>::into(self).into_root()
|
||||||
}
|
}
|
||||||
fn __try_from_root(root: Self::__Root) -> Result<Self, Self::__Root> {
|
|
||||||
let parent = <Self as Extends>::Parent::try_from_root(root)?;
|
|
||||||
parent.clone().try_into().map_err(|_| parent.into_root())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::future::Future;
|
|
||||||
|
use never::Never;
|
||||||
|
|
||||||
use super::coding::Coding;
|
use super::coding::Coding;
|
||||||
use crate::helpers::enc_vec;
|
use crate::helpers::enc_vec;
|
||||||
@@ -8,20 +9,22 @@ pub trait Request: fmt::Debug + Sized + 'static {
|
|||||||
type Response: fmt::Debug + Coding + 'static;
|
type Response: fmt::Debug + Coding + 'static;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn respond<R: Request>(_: &R, rep: R::Response) -> Vec<u8> { enc_vec(&rep).await }
|
pub fn respond<R: Request>(_: &R, rep: R::Response) -> Vec<u8> { enc_vec(&rep) }
|
||||||
pub async fn respond_with<R: Request, F: Future<Output = R::Response>>(
|
|
||||||
r: &R,
|
|
||||||
f: impl FnOnce(&R) -> F,
|
|
||||||
) -> Vec<u8> {
|
|
||||||
respond(r, f(r).await).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Channel: 'static {
|
pub trait Channel: 'static {
|
||||||
type Req: Coding + Sized + 'static;
|
type Req: Coding + Sized + 'static;
|
||||||
type Notif: Coding + Sized + 'static;
|
type Notif: Coding + Sized + 'static;
|
||||||
}
|
}
|
||||||
|
impl Channel for Never {
|
||||||
|
type Notif = Never;
|
||||||
|
type Req = Never;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait MsgSet: Sync + 'static {
|
pub trait MsgSet: Sync + 'static {
|
||||||
type In: Channel;
|
type In: Channel;
|
||||||
type Out: Channel;
|
type Out: Channel;
|
||||||
}
|
}
|
||||||
|
impl MsgSet for Never {
|
||||||
|
type In = Never;
|
||||||
|
type Out = Never;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ edition = "2024"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ordered-float = "5.0.0"
|
ordered-float = "5.1.0"
|
||||||
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
|
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
|
||||||
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
|
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
|
||||||
futures = { version = "0.3.31", features = ["std"], default-features = false }
|
futures = { version = "0.3.31", features = ["std"], default-features = false }
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
|
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
test_executors = "0.3.5"
|
test_executors = "0.4.1"
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ use itertools::Itertools;
|
|||||||
use orchid_api_derive::{Coding, Hierarchy};
|
use orchid_api_derive::{Coding, Hierarchy};
|
||||||
use orchid_api_traits::Request;
|
use orchid_api_traits::Request;
|
||||||
|
|
||||||
use crate::{
|
use crate::{ExprTicket, Expression, ExtHostReq, FormattingUnit, HostExtReq, SysId, TStrv};
|
||||||
ExprTicket, Expression, ExtHostReq, FormattingUnit, HostExtReq, OrcResult, SysId, TStrv,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Coding)]
|
#[derive(Clone, Coding)]
|
||||||
pub struct AtomData(pub Vec<u8>);
|
pub struct AtomData(pub Vec<u8>);
|
||||||
@@ -49,16 +47,16 @@ pub struct Atom {
|
|||||||
/// Construction is always explicit and atoms are never cloned.
|
/// Construction is always explicit and atoms are never cloned.
|
||||||
///
|
///
|
||||||
/// Atoms with `drop == None` are also known as trivial, they can be
|
/// Atoms with `drop == None` are also known as trivial, they can be
|
||||||
/// duplicated and stored with no regard to expression lifetimes. NOTICE
|
/// duplicated and stored with no regard to expression lifetimes. Note
|
||||||
/// that this only applies to the atom. If it's referenced with an
|
/// that this only applies to the atom. If it's referenced with an
|
||||||
/// [ExprTicket], the ticket itself can still expire.
|
/// [ExprTicket], the ticket itself can still expire.
|
||||||
///
|
///
|
||||||
/// Notice also that the atoms still expire when the system is dropped, and
|
/// Note also that the atoms still expire when the system is dropped, and
|
||||||
/// are not portable across instances of the same system, so this doesn't
|
/// are not portable across instances of the same system, so this doesn't
|
||||||
/// imply that the atom is serializable.
|
/// imply that the atom is serializable.
|
||||||
pub drop: Option<AtomId>,
|
pub drop: Option<AtomId>,
|
||||||
/// Data stored in the atom. This could be a key into a map, or the raw data
|
/// Data stored in the atom. This could be a key into a map, the raw data
|
||||||
/// of the atom if it isn't too big.
|
/// of the atom if it isn't too big, or even a pointer.
|
||||||
pub data: AtomData,
|
pub data: AtomData,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +89,7 @@ impl Request for SerializeAtom {
|
|||||||
#[extends(HostExtReq)]
|
#[extends(HostExtReq)]
|
||||||
pub struct DeserAtom(pub SysId, pub Vec<u8>, pub Vec<ExprTicket>);
|
pub struct DeserAtom(pub SysId, pub Vec<u8>, pub Vec<ExprTicket>);
|
||||||
impl Request for DeserAtom {
|
impl Request for DeserAtom {
|
||||||
type Response = Atom;
|
type Response = LocalAtom;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A request blindly routed to the system that provides an atom.
|
/// A request blindly routed to the system that provides an atom.
|
||||||
@@ -104,23 +102,15 @@ impl Request for Fwded {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Coding, Hierarchy)]
|
#[derive(Clone, Debug, Coding, Hierarchy)]
|
||||||
#[extends(ExtHostReq)]
|
#[extends(ExtHostReq)]
|
||||||
pub struct Fwd(pub Atom, pub TStrv, pub Vec<u8>);
|
pub struct Fwd {
|
||||||
|
pub target: Atom,
|
||||||
|
pub method: TStrv,
|
||||||
|
pub body: Vec<u8>,
|
||||||
|
}
|
||||||
impl Request for Fwd {
|
impl Request for Fwd {
|
||||||
type Response = Option<Vec<u8>>;
|
type Response = Option<Vec<u8>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Coding)]
|
|
||||||
pub enum NextStep {
|
|
||||||
Continue(Expression),
|
|
||||||
Halt,
|
|
||||||
}
|
|
||||||
#[derive(Clone, Debug, Coding, Hierarchy)]
|
|
||||||
#[extends(AtomReq, HostExtReq)]
|
|
||||||
pub struct Command(pub Atom);
|
|
||||||
impl Request for Command {
|
|
||||||
type Response = OrcResult<NextStep>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Notification that an atom is being dropped because its associated expression
|
/// Notification that an atom is being dropped because its associated expression
|
||||||
/// isn't referenced anywhere. This should have no effect if the atom's `drop`
|
/// isn't referenced anywhere. This should have no effect if the atom's `drop`
|
||||||
/// flag is false.
|
/// flag is false.
|
||||||
@@ -153,7 +143,6 @@ pub enum AtomReq {
|
|||||||
CallRef(CallRef),
|
CallRef(CallRef),
|
||||||
FinalCall(FinalCall),
|
FinalCall(FinalCall),
|
||||||
Fwded(Fwded),
|
Fwded(Fwded),
|
||||||
Command(Command),
|
|
||||||
AtomPrint(AtomPrint),
|
AtomPrint(AtomPrint),
|
||||||
SerializeAtom(SerializeAtom),
|
SerializeAtom(SerializeAtom),
|
||||||
}
|
}
|
||||||
@@ -163,7 +152,6 @@ impl AtomReq {
|
|||||||
pub fn get_atom(&self) -> &Atom {
|
pub fn get_atom(&self) -> &Atom {
|
||||||
match self {
|
match self {
|
||||||
Self::CallRef(CallRef(a, ..))
|
Self::CallRef(CallRef(a, ..))
|
||||||
| Self::Command(Command(a))
|
|
||||||
| Self::FinalCall(FinalCall(a, ..))
|
| Self::FinalCall(FinalCall(a, ..))
|
||||||
| Self::Fwded(Fwded(a, ..))
|
| Self::Fwded(Fwded(a, ..))
|
||||||
| Self::AtomPrint(AtomPrint(a))
|
| Self::AtomPrint(AtomPrint(a))
|
||||||
|
|||||||
91
orchid-api/src/binary.rs
Normal file
91
orchid-api/src/binary.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
//! # Binary extension definition
|
||||||
|
//!
|
||||||
|
//! A binary extension is a DLL / shared object / dylib with a symbol called
|
||||||
|
//! `orchid_extension_main` which accepts a single argument of type
|
||||||
|
//! [ExtensionContext]. Once that is received, communication continuees through
|
||||||
|
//! the channel with the same protocol outlined in [crate::proto]
|
||||||
|
|
||||||
|
use unsync_pipe::{Reader, Writer};
|
||||||
|
/// !Send !Sync owned waker
|
||||||
|
///
|
||||||
|
/// This object is [Clone] for convenience but it has `drop` and no `clone` so
|
||||||
|
/// interactions must reflect a single logical owner
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct OwnedWakerBin {
|
||||||
|
pub data: *const (),
|
||||||
|
/// `self`
|
||||||
|
pub drop: extern "C" fn(*const ()),
|
||||||
|
/// `self`
|
||||||
|
pub wake: extern "C" fn(*const ()),
|
||||||
|
/// `&self`
|
||||||
|
pub wake_ref: extern "C" fn(*const ()),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// !Send !Sync, equivalent to `&mut Context<'a>`, hence no `drop`.
|
||||||
|
/// When received in [FutureBin::poll], it must not outlive the call.
|
||||||
|
///
|
||||||
|
/// You cannot directly wake using this waker, because such a trampoline would
|
||||||
|
/// pass through the binary interface twice for no reason. An efficient
|
||||||
|
/// implementation should implement that trampoline action internally, whereas
|
||||||
|
/// an inefficient but compliant implementation can clone a fresh waker and use
|
||||||
|
/// it up.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FutureContextBin {
|
||||||
|
pub data: *const (),
|
||||||
|
/// `&self`
|
||||||
|
pub waker: extern "C" fn(*const ()) -> OwnedWakerBin,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ABI-stable `Poll<()>`
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub enum UnitPoll {
|
||||||
|
Pending,
|
||||||
|
Ready,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ABI-stable `Pin<Box<dyn Future<Output = ()>>>`
|
||||||
|
///
|
||||||
|
/// This object is [Clone] for convenience, but it has `drop` and no `clone` so
|
||||||
|
/// interactions must reflect a single logical owner
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct FutureBin {
|
||||||
|
pub data: *const (),
|
||||||
|
/// `self`
|
||||||
|
pub drop: extern "C" fn(*const ()),
|
||||||
|
/// `&mut self` Equivalent to [Future::poll]
|
||||||
|
pub poll: extern "C" fn(*const (), FutureContextBin) -> UnitPoll,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle for a runtime that allows its holder to spawn futures across dynamic
|
||||||
|
/// library boundaries
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct SpawnerBin {
|
||||||
|
pub data: *const (),
|
||||||
|
/// `self`
|
||||||
|
pub drop: extern "C" fn(*const ()),
|
||||||
|
/// `&self` Add a future to this extension's task after a configurable delay
|
||||||
|
/// measured in milliseconds. By itself, a pending timer never prevents
|
||||||
|
/// extension shutdown.
|
||||||
|
pub spawn: extern "C" fn(*const (), u64, FutureBin),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extension context.
|
||||||
|
///
|
||||||
|
/// This struct is a plain old value, all of the contained values have a
|
||||||
|
/// distinct `drop` member
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct ExtensionContext {
|
||||||
|
/// Spawns tasks associated with this extension
|
||||||
|
pub spawner: SpawnerBin,
|
||||||
|
/// serialized [crate::HostExtChannel]
|
||||||
|
pub input: Reader,
|
||||||
|
/// serialized [crate::ExtHostChannel]
|
||||||
|
pub output: Writer,
|
||||||
|
/// UTF-8 log stream directly to log service.
|
||||||
|
pub log: Writer,
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use std::num::NonZeroU16;
|
use std::num::NonZeroU16;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use orchid_api_derive::Coding;
|
use orchid_api_derive::Coding;
|
||||||
@@ -28,7 +29,7 @@ pub struct OrcError {
|
|||||||
pub description: TStr,
|
pub description: TStr,
|
||||||
/// Specific information about the exact error, preferably containing concrete
|
/// Specific information about the exact error, preferably containing concrete
|
||||||
/// values.
|
/// values.
|
||||||
pub message: Arc<String>,
|
pub message: Rc<String>,
|
||||||
/// Specific code fragments that have contributed to the emergence of the
|
/// Specific code fragments that have contributed to the emergence of the
|
||||||
/// error.
|
/// error.
|
||||||
pub locations: Vec<ErrLocation>,
|
pub locations: Vec<ErrLocation>,
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ use std::num::NonZeroU64;
|
|||||||
use orchid_api_derive::{Coding, Hierarchy};
|
use orchid_api_derive::{Coding, Hierarchy};
|
||||||
use orchid_api_traits::Request;
|
use orchid_api_traits::Request;
|
||||||
|
|
||||||
use crate::{Atom, ExtHostNotif, ExtHostReq, Location, OrcError, SysId, TStrv};
|
use crate::{
|
||||||
|
Atom, ExtHostNotif, ExtHostReq, FormattingUnit, LocalAtom, Location, OrcError, SysId, TStrv,
|
||||||
|
};
|
||||||
|
|
||||||
/// An arbitrary ID associated with an expression on the host side. Incoming
|
/// An arbitrary ID associated with an expression on the host side. Incoming
|
||||||
/// tickets always come with some lifetime guarantee, which can be extended with
|
/// tickets always come with some lifetime guarantee, which can be extended with
|
||||||
@@ -65,8 +67,7 @@ pub enum ExpressionKind {
|
|||||||
/// Insert a new atom in the tree. When the clause is used in the const tree,
|
/// Insert a new atom in the tree. When the clause is used in the const tree,
|
||||||
/// the atom must be trivial. This is always a newly constructed atom, if you
|
/// the atom must be trivial. This is always a newly constructed atom, if you
|
||||||
/// want to reference an existing atom, use the corresponding [ExprTicket].
|
/// want to reference an existing atom, use the corresponding [ExprTicket].
|
||||||
/// Because the atom is newly constructed, it also must belong to this system.
|
NewAtom(LocalAtom),
|
||||||
NewAtom(Atom),
|
|
||||||
/// A reference to a constant
|
/// A reference to a constant
|
||||||
Const(TStrv),
|
Const(TStrv),
|
||||||
/// A static runtime error.
|
/// A static runtime error.
|
||||||
@@ -105,11 +106,21 @@ impl Request for Inspect {
|
|||||||
type Response = Inspected;
|
type Response = Inspected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
|
||||||
|
#[extends(ExprReq, ExtHostReq)]
|
||||||
|
pub struct ExprPrint {
|
||||||
|
pub target: ExprTicket,
|
||||||
|
}
|
||||||
|
impl Request for ExprPrint {
|
||||||
|
type Response = FormattingUnit;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Coding, Hierarchy)]
|
#[derive(Clone, Debug, Coding, Hierarchy)]
|
||||||
#[extends(ExtHostReq)]
|
#[extends(ExtHostReq)]
|
||||||
#[extendable]
|
#[extendable]
|
||||||
pub enum ExprReq {
|
pub enum ExprReq {
|
||||||
Inspect(Inspect),
|
Inspect(Inspect),
|
||||||
|
ExprPrint(ExprPrint),
|
||||||
Create(Create),
|
Create(Create),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +134,7 @@ pub enum ExprNotif {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Coding, Hierarchy)]
|
#[derive(Clone, Debug, Coding, Hierarchy)]
|
||||||
#[extends(ExprReq, ExtHostReq)]
|
#[extends(ExprReq, ExtHostReq)]
|
||||||
pub struct Create(pub Expression);
|
pub struct Create(pub SysId, pub Expression);
|
||||||
impl Request for Create {
|
impl Request for Create {
|
||||||
type Response = ExprTicket;
|
type Response = ExprTicket;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::num::NonZeroU64;
|
|||||||
use orchid_api_derive::{Coding, Hierarchy};
|
use orchid_api_derive::{Coding, Hierarchy};
|
||||||
use orchid_api_traits::Request;
|
use orchid_api_traits::Request;
|
||||||
|
|
||||||
use crate::{ExtHostReq, HostExtReq};
|
use crate::{ExtHostNotif, ExtHostReq, HostExtReq};
|
||||||
|
|
||||||
/// Intern requests sent by the replica to the master. These requests are
|
/// Intern requests sent by the replica to the master. These requests are
|
||||||
/// repeatable.
|
/// repeatable.
|
||||||
@@ -71,18 +71,21 @@ pub struct TStr(pub NonZeroU64);
|
|||||||
pub struct TStrv(pub NonZeroU64);
|
pub struct TStrv(pub NonZeroU64);
|
||||||
|
|
||||||
/// A request to sweep the replica. The master will not be sweeped until all
|
/// A request to sweep the replica. The master will not be sweeped until all
|
||||||
/// replicas respond, as it must retain everything the replicas retained
|
/// replicas respond. For efficiency, replicas should make sure to send the
|
||||||
|
/// [Sweeped] notif before returning.
|
||||||
#[derive(Clone, Copy, Debug, Coding, Hierarchy)]
|
#[derive(Clone, Copy, Debug, Coding, Hierarchy)]
|
||||||
#[extends(HostExtReq)]
|
#[extends(HostExtReq)]
|
||||||
pub struct Sweep;
|
pub struct Sweep;
|
||||||
impl Request for Sweep {
|
impl Request for Sweep {
|
||||||
type Response = Retained;
|
type Response = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List of keys in this replica that couldn't be sweeped because local
|
/// List of keys in this replica that were removed during a sweep. This may have
|
||||||
/// datastructures reference their value.
|
/// been initiated via a [Sweep] request, but can also be triggered by the
|
||||||
#[derive(Clone, Debug, Coding)]
|
/// replica autonomously.
|
||||||
pub struct Retained {
|
#[derive(Clone, Debug, Coding, Hierarchy)]
|
||||||
|
#[extends(ExtHostNotif)]
|
||||||
|
pub struct Sweeped {
|
||||||
pub strings: Vec<TStr>,
|
pub strings: Vec<TStr>,
|
||||||
pub vecs: Vec<TStrv>,
|
pub vecs: Vec<TStrv>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ impl Request for LexExpr {
|
|||||||
#[derive(Clone, Debug, Coding)]
|
#[derive(Clone, Debug, Coding)]
|
||||||
pub struct LexedExpr {
|
pub struct LexedExpr {
|
||||||
pub pos: u32,
|
pub pos: u32,
|
||||||
pub expr: TokenTree,
|
pub expr: Vec<TokenTree>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Coding, Hierarchy)]
|
#[derive(Clone, Debug, Coding, Hierarchy)]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod binary;
|
||||||
mod lexer;
|
mod lexer;
|
||||||
pub use lexer::*;
|
pub use lexer::*;
|
||||||
mod format;
|
mod format;
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use orchid_api_derive::{Coding, Hierarchy};
|
use orchid_api_derive::{Coding, Hierarchy};
|
||||||
|
|
||||||
use crate::ExtHostNotif;
|
use crate::{ExtHostNotif, TStr};
|
||||||
|
|
||||||
|
/// Describes what to do with a log stream.
|
||||||
|
/// Log streams are unstructured utf8 text unless otherwise stated.
|
||||||
#[derive(Clone, Debug, Coding, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, Coding, PartialEq, Eq, Hash)]
|
||||||
pub enum LogStrategy {
|
pub enum LogStrategy {
|
||||||
StdErr,
|
/// Context-dependent default stream, often stderr
|
||||||
File(String),
|
Default,
|
||||||
|
/// A file on the local filesystem
|
||||||
|
File { path: String, append: bool },
|
||||||
|
/// Discard any log output
|
||||||
Discard,
|
Discard,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Coding)]
|
||||||
|
pub struct Logger {
|
||||||
|
pub routing: HashMap<String, LogStrategy>,
|
||||||
|
pub default: Option<LogStrategy>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Coding, Hierarchy)]
|
#[derive(Clone, Debug, Coding, Hierarchy)]
|
||||||
#[extends(ExtHostNotif)]
|
#[extends(ExtHostNotif)]
|
||||||
pub struct Log(pub String);
|
pub struct Log {
|
||||||
|
pub category: TStr,
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,51 +22,49 @@
|
|||||||
//! be preserved. Toolkits must ensure that the client code is able to observe
|
//! be preserved. Toolkits must ensure that the client code is able to observe
|
||||||
//! the ordering of messages.
|
//! the ordering of messages.
|
||||||
|
|
||||||
|
use std::io;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use futures::{AsyncRead, AsyncWrite};
|
use futures::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
||||||
use orchid_api_derive::{Coding, Hierarchy};
|
use orchid_api_derive::{Coding, Hierarchy};
|
||||||
use orchid_api_traits::{Channel, Decode, Encode, MsgSet, Request, read_exact, write_exact};
|
use orchid_api_traits::{Channel, Decode, Encode, MsgSet, Request, read_exact};
|
||||||
|
|
||||||
use crate::{atom, expr, interner, lexer, logging, parser, system, tree};
|
use crate::{atom, expr, interner, lexer, logging, parser, system, tree};
|
||||||
|
|
||||||
static HOST_INTRO: &[u8] = b"Orchid host, binary API v0\n";
|
static HOST_INTRO: &[u8] = b"Orchid host, binary API v0\n";
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct HostHeader {
|
pub struct HostHeader {
|
||||||
pub log_strategy: logging::LogStrategy,
|
pub logger: logging::Logger,
|
||||||
pub msg_logs: logging::LogStrategy,
|
|
||||||
}
|
}
|
||||||
impl Decode for HostHeader {
|
impl Decode for HostHeader {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
read_exact(read.as_mut(), HOST_INTRO).await;
|
read_exact(read.as_mut(), HOST_INTRO).await?;
|
||||||
Self {
|
Ok(Self { logger: logging::Logger::decode(read).await? })
|
||||||
log_strategy: logging::LogStrategy::decode(read.as_mut()).await,
|
|
||||||
msg_logs: logging::LogStrategy::decode(read.as_mut()).await,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Encode for HostHeader {
|
impl Encode for HostHeader {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
write_exact(write.as_mut(), HOST_INTRO).await;
|
write.write_all(HOST_INTRO).await?;
|
||||||
self.log_strategy.encode(write.as_mut()).await;
|
self.logger.encode(write.as_mut()).await
|
||||||
self.msg_logs.encode(write.as_mut()).await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static EXT_INTRO: &[u8] = b"Orchid extension, binary API v0\n";
|
static EXT_INTRO: &[u8] = b"Orchid extension, binary API v0\n";
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct ExtensionHeader {
|
pub struct ExtensionHeader {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub systems: Vec<system::SystemDecl>,
|
pub systems: Vec<system::SystemDecl>,
|
||||||
}
|
}
|
||||||
impl Decode for ExtensionHeader {
|
impl Decode for ExtensionHeader {
|
||||||
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> Self {
|
async fn decode<R: AsyncRead + ?Sized>(mut read: Pin<&mut R>) -> io::Result<Self> {
|
||||||
read_exact(read.as_mut(), EXT_INTRO).await;
|
read_exact(read.as_mut(), EXT_INTRO).await?;
|
||||||
Self { name: String::decode(read.as_mut()).await, systems: Vec::decode(read).await }
|
Ok(Self { name: String::decode(read.as_mut()).await?, systems: Vec::decode(read).await? })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Encode for ExtensionHeader {
|
impl Encode for ExtensionHeader {
|
||||||
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) {
|
async fn encode<W: AsyncWrite + ?Sized>(&self, mut write: Pin<&mut W>) -> io::Result<()> {
|
||||||
write_exact(write.as_mut(), EXT_INTRO).await;
|
write.write_all(EXT_INTRO).await?;
|
||||||
self.name.encode(write.as_mut()).await;
|
self.name.encode(write.as_mut()).await?;
|
||||||
self.systems.encode(write).await
|
self.systems.encode(write).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,6 +97,7 @@ pub enum ExtHostReq {
|
|||||||
pub enum ExtHostNotif {
|
pub enum ExtHostNotif {
|
||||||
ExprNotif(expr::ExprNotif),
|
ExprNotif(expr::ExprNotif),
|
||||||
Log(logging::Log),
|
Log(logging::Log),
|
||||||
|
Sweeped(interner::Sweeped),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtHostChannel;
|
pub struct ExtHostChannel;
|
||||||
@@ -155,22 +154,22 @@ impl MsgSet for HostMsgSet {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use orchid_api_traits::enc_vec;
|
use orchid_api_traits::enc_vec;
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
use test_executors::spin_on;
|
use test_executors::spin_on;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::Logger;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn host_header_enc() {
|
fn host_header_enc() {
|
||||||
spin_on(async {
|
spin_on(async {
|
||||||
let hh = HostHeader {
|
let hh = HostHeader { logger: Logger { routing: HashMap::new(), default: None } };
|
||||||
log_strategy: logging::LogStrategy::File("SomeFile".to_string()),
|
let mut enc = &enc_vec(&hh)[..];
|
||||||
msg_logs: logging::LogStrategy::File("SomeFile".to_string()),
|
|
||||||
};
|
|
||||||
let mut enc = &enc_vec(&hh).await[..];
|
|
||||||
eprintln!("Encoded to {enc:?}");
|
eprintln!("Encoded to {enc:?}");
|
||||||
HostHeader::decode(Pin::new(&mut enc)).await;
|
HostHeader::decode(Pin::new(&mut enc)).await.unwrap();
|
||||||
assert_eq!(enc, []);
|
assert_eq!(enc, []);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -187,9 +186,9 @@ mod tests {
|
|||||||
priority: NotNan::new(1f64).unwrap(),
|
priority: NotNan::new(1f64).unwrap(),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
let mut enc = &enc_vec(&eh).await[..];
|
let mut enc = &enc_vec(&eh)[..];
|
||||||
eprintln!("Encoded to {enc:?}");
|
eprintln!("Encoded to {enc:?}");
|
||||||
ExtensionHeader::decode(Pin::new(&mut enc)).await;
|
ExtensionHeader::decode(Pin::new(&mut enc)).await.unwrap();
|
||||||
assert_eq!(enc, [])
|
assert_eq!(enc, [])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use std::collections::HashMap;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use orchid_api_derive::{Coding, Hierarchy};
|
use orchid_api_derive::{Coding, Hierarchy};
|
||||||
use orchid_api_traits::Request;
|
use orchid_api_traits::Request;
|
||||||
@@ -48,7 +47,7 @@ pub enum Token {
|
|||||||
/// NewExpr(Bottom) because it fails in dead branches too.
|
/// NewExpr(Bottom) because it fails in dead branches too.
|
||||||
Bottom(Vec<OrcError>),
|
Bottom(Vec<OrcError>),
|
||||||
/// A comment
|
/// A comment
|
||||||
Comment(Rc<String>),
|
Comment(TStr),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Coding)]
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Coding)]
|
||||||
|
|||||||
12
orchid-async-utils/Cargo.toml
Normal file
12
orchid-async-utils/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "orchid-async-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = { version = "0.3.31", default-features = false, features = [
|
||||||
|
"std",
|
||||||
|
"async-await",
|
||||||
|
] }
|
||||||
|
itertools = "0.14.0"
|
||||||
|
task-local = "0.1.0"
|
||||||
185
orchid-async-utils/src/debug.rs
Normal file
185
orchid-async-utils/src/debug.rs
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
//! Note that these utilities are safe and simple in order to facilitate
|
||||||
|
//! debugging without adding more points of failure, but they're not efficient;
|
||||||
|
//! they may perform heap allocations, I/O and other expensive operations, or
|
||||||
|
//! even block the thread altogether waiting for input whenever they receive
|
||||||
|
//! control
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::pin::pin;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::task::{Context, Poll, Wake, Waker};
|
||||||
|
use std::thread::panicking;
|
||||||
|
|
||||||
|
use futures::Stream;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use task_local::task_local;
|
||||||
|
|
||||||
|
struct OnPollWaker<F: Fn() + 'static>(Waker, F);
|
||||||
|
impl<F: Fn() + 'static> Wake for OnPollWaker<F> {
|
||||||
|
fn wake(self: Arc<Self>) {
|
||||||
|
(self.1)();
|
||||||
|
self.0.wake_by_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attach a callback to the [Future] protocol for testing and debugging.
|
||||||
|
pub async fn on_wake<F: Future>(
|
||||||
|
f: F,
|
||||||
|
wake: impl Fn() + Clone + Send + Sync + 'static,
|
||||||
|
) -> F::Output {
|
||||||
|
let mut f = pin!(f);
|
||||||
|
futures::future::poll_fn(|cx| {
|
||||||
|
let waker = Arc::new(OnPollWaker(cx.waker().clone(), wake.clone())).into();
|
||||||
|
f.as_mut().poll(&mut Context::from_waker(&waker))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Respond to [Future::poll] with a callback. For maximum flexibility and state
|
||||||
|
/// control, your callback receives the actual poll job as a callback function.
|
||||||
|
/// Failure to call this function will result in an immediate panic.
|
||||||
|
pub async fn wrap_poll<Fut: Future>(
|
||||||
|
f: Fut,
|
||||||
|
mut cb: impl FnMut(Box<dyn FnOnce() -> bool + '_>),
|
||||||
|
) -> Fut::Output {
|
||||||
|
let mut f = pin!(f);
|
||||||
|
futures::future::poll_fn(|cx| {
|
||||||
|
let poll = RefCell::new(None);
|
||||||
|
cb(Box::new(|| {
|
||||||
|
let poll1 = f.as_mut().poll(cx);
|
||||||
|
let ret = poll1.is_ready();
|
||||||
|
*poll.borrow_mut() = Some(poll1);
|
||||||
|
ret
|
||||||
|
}));
|
||||||
|
poll.into_inner().expect("Callback to on_poll failed to call its argument")
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Respond to [Stream::poll_next] with a callback. The semantics of the
|
||||||
|
/// callback are identical to that in [wrap_poll]
|
||||||
|
pub fn wrap_poll_next<'a, S: Stream + 'a>(
|
||||||
|
s: S,
|
||||||
|
mut cb: impl FnMut(Box<dyn FnOnce() -> bool + '_>) + 'a,
|
||||||
|
) -> impl Stream<Item = S::Item> + 'a {
|
||||||
|
let mut s = Box::pin(s);
|
||||||
|
futures::stream::poll_fn(move |cx| {
|
||||||
|
let poll = RefCell::new(None);
|
||||||
|
cb(Box::new(|| {
|
||||||
|
let poll1 = s.as_mut().poll_next(cx);
|
||||||
|
let ret = poll1.is_ready();
|
||||||
|
*poll.borrow_mut() = Some(poll1);
|
||||||
|
ret
|
||||||
|
}));
|
||||||
|
poll.into_inner().expect("Callback to on_poll failed to call its argument")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attach a callback to the [Stream] protocol for testing and debugging.
|
||||||
|
pub fn on_stream_wake<'a, S: Stream + 'a>(
|
||||||
|
s: S,
|
||||||
|
wake: impl Fn() + Clone + Send + Sync + 'static,
|
||||||
|
) -> impl Stream<Item = S::Item> {
|
||||||
|
let mut s = Box::pin(s);
|
||||||
|
futures::stream::poll_fn(move |cx| {
|
||||||
|
let waker = Arc::new(OnPollWaker(cx.waker().clone(), wake.clone())).into();
|
||||||
|
s.as_mut().poll_next(&mut Context::from_waker(&waker))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
task_local! {
|
||||||
|
static LABEL_STATE: Vec<Rc<String>>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a label to the "label stack" for the duration of a future that helps you
|
||||||
|
/// efficiently visualize important aspects of the call stack during logging
|
||||||
|
pub async fn with_label<Fut: Future>(label: &str, f: Fut) -> Fut::Output {
|
||||||
|
let mut new_lbl = LABEL_STATE.try_with(|lbl| lbl.clone()).unwrap_or_default();
|
||||||
|
new_lbl.push(Rc::new(label.to_string()));
|
||||||
|
LABEL_STATE.scope(new_lbl, f).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows to print the label stack
|
||||||
|
pub fn label() -> impl Display + Clone + Send + Sync + 'static {
|
||||||
|
LABEL_STATE.try_with(|lbl| lbl.iter().join("/")).unwrap_or("".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays the label stack when printed
|
||||||
|
pub struct Label;
|
||||||
|
impl Display for Label {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", label()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches generic eprintln handlers to a future
|
||||||
|
pub async fn eprint_events<Fut: Future>(note: &str, f: Fut) -> Fut::Output {
|
||||||
|
let label = label();
|
||||||
|
let note1 = note.to_string();
|
||||||
|
on_wake(
|
||||||
|
wrap_poll(f, |cb| {
|
||||||
|
eprintln!("{Label} polling {note}");
|
||||||
|
eprintln!("{Label} polled {note} (ready? {})", cb())
|
||||||
|
}),
|
||||||
|
move || eprintln!("{label} woke {note1}"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attaches generic eprintln handlers to a stream
|
||||||
|
pub fn eprint_stream_events<'a, S: Stream + 'a>(
|
||||||
|
note: &'a str,
|
||||||
|
s: S,
|
||||||
|
) -> impl Stream<Item = S::Item> + 'a {
|
||||||
|
let label = label();
|
||||||
|
let note1 = note.to_string();
|
||||||
|
on_stream_wake(
|
||||||
|
wrap_poll_next(s, move |cb| {
|
||||||
|
eprintln!("{Label} polling {note}");
|
||||||
|
eprintln!("{Label} polled {note} (ready? {})", cb())
|
||||||
|
}),
|
||||||
|
move || eprintln!("{label} woke {note1}"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpinWaker(AtomicBool);
|
||||||
|
impl Wake for SpinWaker {
|
||||||
|
fn wake(self: Arc<Self>) { self.0.store(true, Ordering::Relaxed); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A dumb executor that keeps synchronously re-running the future as long as it
|
||||||
|
/// keeps synchronously waking itself. This is useful for deterministic tests
|
||||||
|
/// that don't contain side effects or threading.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the future doesn't wake itself and doesn't settle.
|
||||||
|
pub fn spin_on<Fut: Future>(f: Fut) -> Fut::Output {
|
||||||
|
let repeat = Arc::new(SpinWaker(AtomicBool::new(false)));
|
||||||
|
let mut f = pin!(f);
|
||||||
|
let waker = repeat.clone().into();
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
loop {
|
||||||
|
match f.as_mut().poll(&mut cx) {
|
||||||
|
Poll::Ready(t) => break t,
|
||||||
|
Poll::Pending if repeat.0.swap(false, Ordering::Relaxed) => (),
|
||||||
|
Poll::Pending => panic!("The future did not exit and did not call its waker."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an object that will panic if dropped. [PanicOnDrop::defuse] must be
|
||||||
|
/// called once the particular constraint preventing a drop has passed
|
||||||
|
pub fn assert_no_drop(msg: &'static str) -> PanicOnDrop { PanicOnDrop(true, msg) }
|
||||||
|
|
||||||
|
/// This object will panic if dropped. Call [Self::defuse] when dropping is safe
|
||||||
|
/// again
|
||||||
|
pub struct PanicOnDrop(bool, &'static str);
|
||||||
|
impl PanicOnDrop {
|
||||||
|
/// Allow dropping the object without causing a panic
|
||||||
|
pub fn defuse(mut self) { self.0 = false; }
|
||||||
|
}
|
||||||
|
impl Drop for PanicOnDrop {
|
||||||
|
fn drop(&mut self) { assert!(panicking() || !self.0, "{}", self.1) }
|
||||||
|
}
|
||||||
5
orchid-async-utils/src/lib.rs
Normal file
5
orchid-async-utils/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod debug;
|
||||||
|
mod localset;
|
||||||
|
pub use localset::*;
|
||||||
|
mod task_future;
|
||||||
|
pub use task_future::*;
|
||||||
48
orchid-async-utils/src/localset.rs
Normal file
48
orchid-async-utils/src/localset.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::Poll;
|
||||||
|
|
||||||
|
use futures::StreamExt;
|
||||||
|
use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender, unbounded};
|
||||||
|
use futures::future::LocalBoxFuture;
|
||||||
|
|
||||||
|
pub struct LocalSet<'a, E> {
|
||||||
|
receiver: UnboundedReceiver<LocalBoxFuture<'a, Result<(), E>>>,
|
||||||
|
pending: VecDeque<LocalBoxFuture<'a, Result<(), E>>>,
|
||||||
|
}
|
||||||
|
impl<'a, E> LocalSet<'a, E> {
|
||||||
|
pub fn new() -> (UnboundedSender<LocalBoxFuture<'a, Result<(), E>>>, Self) {
|
||||||
|
let (sender, receiver) = unbounded();
|
||||||
|
(sender, Self { receiver, pending: VecDeque::new() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<E> Future for LocalSet<'_, E> {
|
||||||
|
type Output = Result<(), E>;
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.get_mut();
|
||||||
|
let mut any_pending = false;
|
||||||
|
loop {
|
||||||
|
match this.receiver.poll_next_unpin(cx) {
|
||||||
|
Poll::Ready(Some(fut)) => this.pending.push_back(fut),
|
||||||
|
Poll::Ready(None) => break,
|
||||||
|
Poll::Pending => {
|
||||||
|
any_pending = true;
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let count = this.pending.len();
|
||||||
|
for _ in 0..count {
|
||||||
|
let mut req = this.pending.pop_front().unwrap();
|
||||||
|
match req.as_mut().poll(cx) {
|
||||||
|
Poll::Ready(Ok(())) => (),
|
||||||
|
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
|
||||||
|
Poll::Pending => {
|
||||||
|
any_pending = true;
|
||||||
|
this.pending.push_back(req)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if any_pending { Poll::Pending } else { Poll::Ready(Ok(())) }
|
||||||
|
}
|
||||||
|
}
|
||||||
92
orchid-async-utils/src/task_future.rs
Normal file
92
orchid-async-utils/src/task_future.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
use std::any::Any;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::task::{Context, Poll, Waker};
|
||||||
|
|
||||||
|
use futures::future::{FusedFuture, LocalBoxFuture};
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
work: Option<LocalBoxFuture<'static, Box<dyn Any>>>,
|
||||||
|
result: Option<Box<dyn Any>>,
|
||||||
|
waker: Waker,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fused future that can be passed to a non-polymorphic executor that doesn't
|
||||||
|
/// process results and doesn't return handles
|
||||||
|
pub struct Pollable(Rc<RefCell<State>>);
|
||||||
|
impl FusedFuture for Pollable {
|
||||||
|
fn is_terminated(&self) -> bool {
|
||||||
|
let g = self.0.borrow();
|
||||||
|
g.work.is_none() || g.result.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Future for Pollable {
|
||||||
|
type Output = ();
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let mut g = self.0.borrow_mut();
|
||||||
|
match &mut *g {
|
||||||
|
State { result: Some(_), .. } | State { work: None, .. } => Poll::Ready(()),
|
||||||
|
State { work: Some(work), waker, result } => match work.as_mut().poll(cx) {
|
||||||
|
Poll::Pending => {
|
||||||
|
waker.clone_from(cx.waker());
|
||||||
|
Poll::Pending
|
||||||
|
},
|
||||||
|
Poll::Ready(val) => {
|
||||||
|
*result = Some(val);
|
||||||
|
g.work = None;
|
||||||
|
Poll::Ready(())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An object that can be used to inspect the state of the task
|
||||||
|
pub struct Handle<T: 'static>(Rc<RefCell<State>>, PhantomData<T>);
|
||||||
|
impl<T: 'static> Handle<T> {
|
||||||
|
/// Immediately stop working on this task, and return the result if it has
|
||||||
|
/// already finished
|
||||||
|
pub fn abort(&self) -> Option<T> {
|
||||||
|
let mut g = self.0.borrow_mut();
|
||||||
|
g.work.take();
|
||||||
|
match g.result.take() {
|
||||||
|
Some(val) => Some(*val.downcast().expect("Mismatch between type of future and handle")),
|
||||||
|
None => {
|
||||||
|
g.waker.wake_by_ref();
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Determine if there's any more work to do on this task
|
||||||
|
pub fn is_finished(&self) -> bool {
|
||||||
|
let g = self.0.borrow();
|
||||||
|
g.result.is_some() || g.work.is_none()
|
||||||
|
}
|
||||||
|
/// "finish" the freestanding task, and return the future instead
|
||||||
|
pub async fn join(self) -> T {
|
||||||
|
let work = {
|
||||||
|
let mut g = self.0.borrow_mut();
|
||||||
|
if let Some(val) = g.result.take() {
|
||||||
|
return *val.downcast().expect("Mistmatch between type of future and handle");
|
||||||
|
}
|
||||||
|
g.waker.wake_by_ref();
|
||||||
|
g.work.take().expect("Attempted to join task that was already aborted")
|
||||||
|
};
|
||||||
|
*work.await.downcast().expect("Mismatch between type of future and handle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Split a future into an object that can be polled and one that returns
|
||||||
|
/// information on its progress and its result. The first one can be passed to
|
||||||
|
/// an executor or localset, the second can be used to manage it
|
||||||
|
pub fn to_task<F: Future<Output: 'static> + 'static>(f: F) -> (Pollable, Handle<F::Output>) {
|
||||||
|
let dyn_future = Box::pin(async { Box::new(f.await) as Box<dyn Any> });
|
||||||
|
let state = Rc::new(RefCell::new(State {
|
||||||
|
result: None,
|
||||||
|
work: Some(dyn_future),
|
||||||
|
waker: Waker::noop().clone(),
|
||||||
|
}));
|
||||||
|
(Pollable(state.clone()), Handle(state, PhantomData))
|
||||||
|
}
|
||||||
@@ -6,12 +6,18 @@ edition = "2024"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
|
||||||
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
|
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
|
||||||
|
orchid-async-utils = { version = "0.1.0", path = "../orchid-async-utils" }
|
||||||
async-once-cell = "0.5.4"
|
async-once-cell = "0.5.4"
|
||||||
|
bound = "0.6.0"
|
||||||
derive_destructure = "1.0.0"
|
derive_destructure = "1.0.0"
|
||||||
dyn-clone = "1.0.20"
|
dyn-clone = "1.0.20"
|
||||||
futures = { version = "0.3.31", features = ["std"], default-features = false }
|
futures = { version = "0.3.31", default-features = false, features = [
|
||||||
hashbrown = "0.16.0"
|
"std",
|
||||||
|
"async-await",
|
||||||
|
] }
|
||||||
|
hashbrown = "0.16.1"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
never = "0.1.0"
|
never = "0.1.0"
|
||||||
@@ -19,10 +25,14 @@ num-traits = "0.2.19"
|
|||||||
orchid-api = { version = "0.1.0", path = "../orchid-api" }
|
orchid-api = { version = "0.1.0", path = "../orchid-api" }
|
||||||
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
|
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
|
||||||
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
|
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
|
||||||
ordered-float = "5.0.0"
|
ordered-float = "5.1.0"
|
||||||
regex = "1.11.2"
|
regex = "1.12.2"
|
||||||
rust-embed = "8.7.2"
|
rust-embed = "8.9.0"
|
||||||
some_executor = "0.6.1"
|
|
||||||
substack = "1.1.1"
|
substack = "1.1.1"
|
||||||
test_executors = "0.3.5"
|
|
||||||
trait-set = "0.3.0"
|
trait-set = "0.3.0"
|
||||||
|
task-local = "0.1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
futures = "0.3.31"
|
||||||
|
rand = "0.10.0"
|
||||||
|
rand_chacha = "0.10.0"
|
||||||
|
|||||||
122
orchid-base/src/binary.rs
Normal file
122
orchid-base/src/binary.rs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
use std::mem;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
||||||
|
|
||||||
|
use crate::api;
|
||||||
|
|
||||||
|
type WideBox = Box<dyn Future<Output = ()>>;
|
||||||
|
|
||||||
|
static OWNED_VTABLE: RawWakerVTable = RawWakerVTable::new(
|
||||||
|
|data| {
|
||||||
|
let data = unsafe { Rc::<api::binary::OwnedWakerBin>::from_raw(data as *const _) };
|
||||||
|
let val = RawWaker::new(Rc::into_raw(data.clone()) as *const (), &OWNED_VTABLE);
|
||||||
|
// Clone must create a duplicate of the Rc, so it has to be un-leaked, cloned,
|
||||||
|
// then leaked again.
|
||||||
|
let _ = Rc::into_raw(data);
|
||||||
|
val
|
||||||
|
},
|
||||||
|
|data| {
|
||||||
|
// Wake must awaken the task and then clean up the state, so the waker must be
|
||||||
|
// un-leaked
|
||||||
|
let data = unsafe { Rc::<api::binary::OwnedWakerBin>::from_raw(data as *const _) };
|
||||||
|
(data.wake)(data.data);
|
||||||
|
mem::drop(data);
|
||||||
|
},
|
||||||
|
|data| {
|
||||||
|
// Wake-by-ref must awaken the task while preserving the future, so the Rc is
|
||||||
|
// untouched
|
||||||
|
let data = unsafe { (data as *const api::binary::OwnedWakerBin).as_ref() }.unwrap();
|
||||||
|
(data.wake_ref)(data.data);
|
||||||
|
},
|
||||||
|
|data| {
|
||||||
|
// Drop must clean up the state, so the waker must be un-leaked
|
||||||
|
let data = unsafe { Rc::<api::binary::OwnedWakerBin>::from_raw(data as *const _) };
|
||||||
|
(data.drop)(data.data);
|
||||||
|
mem::drop(data);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
struct BorrowedWakerData<'a> {
|
||||||
|
go_around: &'a mut bool,
|
||||||
|
cx: api::binary::FutureContextBin,
|
||||||
|
}
|
||||||
|
static BORROWED_VTABLE: RawWakerVTable = RawWakerVTable::new(
|
||||||
|
|data| {
|
||||||
|
let data = unsafe { (data as *mut BorrowedWakerData).as_mut() }.unwrap();
|
||||||
|
let owned_data = Rc::<api::binary::OwnedWakerBin>::new((data.cx.waker)(data.cx.data));
|
||||||
|
RawWaker::new(Rc::into_raw(owned_data) as *const (), &OWNED_VTABLE)
|
||||||
|
},
|
||||||
|
|data| *unsafe { (data as *mut BorrowedWakerData).as_mut() }.unwrap().go_around = true,
|
||||||
|
|data| *unsafe { (data as *mut BorrowedWakerData).as_mut() }.unwrap().go_around = true,
|
||||||
|
|_data| {},
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Convert a future to a binary-compatible format that can be sent across
|
||||||
|
/// dynamic library boundaries
|
||||||
|
#[must_use]
|
||||||
|
pub fn future_to_vt<Fut: Future<Output = ()> + 'static>(fut: Fut) -> api::binary::FutureBin {
|
||||||
|
let wide_box = Box::new(fut) as WideBox;
|
||||||
|
let data = Box::into_raw(Box::new(wide_box));
|
||||||
|
extern "C" fn drop(raw: *const ()) {
|
||||||
|
mem::drop(unsafe { Box::<WideBox>::from_raw(raw as *mut _) })
|
||||||
|
}
|
||||||
|
extern "C" fn poll(raw: *const (), cx: api::binary::FutureContextBin) -> api::binary::UnitPoll {
|
||||||
|
let mut this = unsafe { Pin::new_unchecked(&mut **(raw as *mut WideBox).as_mut().unwrap()) };
|
||||||
|
loop {
|
||||||
|
let mut go_around = false;
|
||||||
|
let borrowed_waker = unsafe {
|
||||||
|
Waker::from_raw(RawWaker::new(
|
||||||
|
&mut BorrowedWakerData { go_around: &mut go_around, cx } as *mut _ as *const (),
|
||||||
|
&BORROWED_VTABLE,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
let mut ctx = Context::from_waker(&borrowed_waker);
|
||||||
|
let result = this.as_mut().poll(&mut ctx);
|
||||||
|
if matches!(result, Poll::Ready(())) {
|
||||||
|
break api::binary::UnitPoll::Ready;
|
||||||
|
}
|
||||||
|
if !go_around {
|
||||||
|
break api::binary::UnitPoll::Pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
api::binary::FutureBin { data: data as *const _, drop, poll }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VirtualFuture {
|
||||||
|
vt: api::binary::FutureBin,
|
||||||
|
}
|
||||||
|
impl Unpin for VirtualFuture {}
|
||||||
|
impl Future for VirtualFuture {
|
||||||
|
type Output = ();
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
extern "C" fn waker(raw: *const ()) -> api::binary::OwnedWakerBin {
|
||||||
|
let waker = unsafe { (raw as *mut Context).as_mut() }.unwrap().waker().clone();
|
||||||
|
let data = Box::into_raw(Box::<Waker>::new(waker)) as *const ();
|
||||||
|
return api::binary::OwnedWakerBin { data, drop, wake, wake_ref };
|
||||||
|
extern "C" fn drop(raw: *const ()) {
|
||||||
|
mem::drop(unsafe { Box::<Waker>::from_raw(raw as *mut Waker) })
|
||||||
|
}
|
||||||
|
extern "C" fn wake(raw: *const ()) {
|
||||||
|
unsafe { Box::<Waker>::from_raw(raw as *mut Waker) }.wake();
|
||||||
|
}
|
||||||
|
extern "C" fn wake_ref(raw: *const ()) {
|
||||||
|
unsafe { (raw as *mut Waker).as_mut() }.unwrap().wake_by_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let cx = api::binary::FutureContextBin { data: cx as *mut Context as *const (), waker };
|
||||||
|
let result = (self.vt.poll)(self.vt.data, cx);
|
||||||
|
match result {
|
||||||
|
api::binary::UnitPoll::Pending => Poll::Pending,
|
||||||
|
api::binary::UnitPoll::Ready => Poll::Ready(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for VirtualFuture {
|
||||||
|
fn drop(&mut self) { (self.vt.drop)(self.vt.data) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive a future sent across dynamic library boundaries and convert it into
|
||||||
|
/// an owned object
|
||||||
|
pub fn vt_to_future(vt: api::binary::FutureBin) -> impl Future<Output = ()> { VirtualFuture { vt } }
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
use std::borrow::Borrow;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub enum ArcCow<'a, T: ?Sized + ToOwned> {
|
|
||||||
Borrowed(&'a T),
|
|
||||||
Owned(Arc<T::Owned>),
|
|
||||||
}
|
|
||||||
impl<T: ?Sized + ToOwned> ArcCow<'_, T> {
|
|
||||||
pub fn owned(value: T::Owned) -> Self { Self::Owned(Arc::new(value)) }
|
|
||||||
}
|
|
||||||
impl<T: ?Sized + ToOwned> Clone for ArcCow<'_, T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Borrowed(r) => Self::Borrowed(r),
|
|
||||||
Self::Owned(b) => Self::Owned(b.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: ?Sized + ToOwned> Deref for ArcCow<'_, T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
match self {
|
|
||||||
Self::Borrowed(t) => t,
|
|
||||||
Self::Owned(b) => b.as_ref().borrow(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,20 +3,22 @@
|
|||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
/// A trait object of [Iterator] to be assigned to variables that may be
|
/// A trait object of [Iterator] to be assigned to variables that may be
|
||||||
/// initialized form multiple iterators of incompatible types
|
/// initialized form iterators of multiple or unknown types
|
||||||
pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
|
pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
|
||||||
/// creates a [BoxedIter] of a single element
|
/// creates a [BoxedIter] of a single element
|
||||||
|
#[must_use]
|
||||||
pub fn box_once<'a, T: 'a>(t: T) -> BoxedIter<'a, T> { Box::new(iter::once(t)) }
|
pub fn box_once<'a, T: 'a>(t: T) -> BoxedIter<'a, T> { Box::new(iter::once(t)) }
|
||||||
/// creates an empty [BoxedIter]
|
/// creates an empty [BoxedIter]
|
||||||
|
#[must_use]
|
||||||
pub fn box_empty<'a, T: 'a>() -> BoxedIter<'a, T> { Box::new(iter::empty()) }
|
pub fn box_empty<'a, T: 'a>() -> BoxedIter<'a, T> { Box::new(iter::empty()) }
|
||||||
|
|
||||||
/// Chain various iterators into a [BoxedIter]
|
/// Chain various iterators into a [BoxedIter]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! box_chain {
|
macro_rules! box_chain {
|
||||||
($curr:expr) => {
|
($curr:expr) => {
|
||||||
Box::new($curr) as $crate::boxed_iter::BoxedIter<_>
|
Box::new($curr) as $crate::BoxedIter<_>
|
||||||
};
|
};
|
||||||
($curr:expr, $($rest:expr),*) => {
|
($curr:expr, $($rest:expr),*) => {
|
||||||
Box::new($curr$(.chain($rest))*) as $crate::boxed_iter::BoxedIter<_>
|
Box::new($curr$(.chain($rest))*) as $crate::BoxedIter<_>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
use std::ops::Deref;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use futures::future::LocalBoxFuture;
|
|
||||||
|
|
||||||
use crate::api;
|
|
||||||
|
|
||||||
pub type Spawner = Rc<dyn Fn(LocalBoxFuture<'static, ()>)>;
|
|
||||||
|
|
||||||
/// The 3 primary contact points with an extension are
|
|
||||||
/// - send a message
|
|
||||||
/// - wait for a message to arrive
|
|
||||||
/// - wait for the extension to stop after exit (this is the implicit Drop)
|
|
||||||
///
|
|
||||||
/// There are no ordering guarantees about these
|
|
||||||
pub trait ExtPort {
|
|
||||||
#[must_use]
|
|
||||||
fn send<'a>(&'a self, msg: &'a [u8]) -> LocalBoxFuture<'a, ()>;
|
|
||||||
#[must_use]
|
|
||||||
fn recv(&self) -> LocalBoxFuture<'_, Option<Vec<u8>>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ExtInit {
|
|
||||||
pub header: api::ExtensionHeader,
|
|
||||||
pub port: Box<dyn ExtPort>,
|
|
||||||
}
|
|
||||||
impl ExtInit {
|
|
||||||
pub async fn send(&self, msg: &[u8]) { self.port.send(msg).await }
|
|
||||||
pub async fn recv(&self) -> Option<Vec<u8>> { self.port.recv().await }
|
|
||||||
}
|
|
||||||
impl Deref for ExtInit {
|
|
||||||
type Target = api::ExtensionHeader;
|
|
||||||
fn deref(&self) -> &Self::Target { &self.header }
|
|
||||||
}
|
|
||||||
@@ -5,9 +5,9 @@ use itertools::Itertools;
|
|||||||
|
|
||||||
use crate::api;
|
use crate::api;
|
||||||
|
|
||||||
pub type CRange = RangeInclusive<char>;
|
/// A fast character filter to avoid superfluous extension calls in the lexer.
|
||||||
|
|
||||||
pub trait ICFilter: fmt::Debug {
|
pub trait ICFilter: fmt::Debug {
|
||||||
|
/// Returns an ordered set of character ranges
|
||||||
fn ranges(&self) -> &[RangeInclusive<char>];
|
fn ranges(&self) -> &[RangeInclusive<char>];
|
||||||
}
|
}
|
||||||
impl ICFilter for [RangeInclusive<char>] {
|
impl ICFilter for [RangeInclusive<char>] {
|
||||||
@@ -17,7 +17,10 @@ impl ICFilter for api::CharFilter {
|
|||||||
fn ranges(&self) -> &[RangeInclusive<char>] { &self.0 }
|
fn ranges(&self) -> &[RangeInclusive<char>] { &self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_merge_char_ranges(left: CRange, right: CRange) -> Result<CRange, (CRange, CRange)> {
|
fn try_merge_char_ranges(
|
||||||
|
left: RangeInclusive<char>,
|
||||||
|
right: RangeInclusive<char>,
|
||||||
|
) -> Result<RangeInclusive<char>, (RangeInclusive<char>, RangeInclusive<char>)> {
|
||||||
match *left.end() as u32 + 1 < *right.start() as u32 {
|
match *left.end() as u32 + 1 < *right.start() as u32 {
|
||||||
true => Err((left, right)),
|
true => Err((left, right)),
|
||||||
false => Ok(*left.start()..=*right.end()),
|
false => Ok(*left.start()..=*right.end()),
|
||||||
@@ -25,8 +28,9 @@ fn try_merge_char_ranges(left: CRange, right: CRange) -> Result<CRange, (CRange,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Process the character ranges to make them adhere to the structural
|
/// Process the character ranges to make them adhere to the structural
|
||||||
/// requirements of [CharFilter]
|
/// requirements of [api::CharFilter]
|
||||||
pub fn mk_char_filter(items: impl IntoIterator<Item = CRange>) -> api::CharFilter {
|
#[must_use]
|
||||||
|
pub fn mk_char_filter(items: impl IntoIterator<Item = RangeInclusive<char>>) -> api::CharFilter {
|
||||||
api::CharFilter(
|
api::CharFilter(
|
||||||
(items.into_iter())
|
(items.into_iter())
|
||||||
.filter(|r| *r.start() as u32 <= *r.end() as u32)
|
.filter(|r| *r.start() as u32 <= *r.end() as u32)
|
||||||
@@ -37,6 +41,7 @@ pub fn mk_char_filter(items: impl IntoIterator<Item = CRange>) -> api::CharFilte
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Decide whether a char filter matches a character via binary search
|
/// Decide whether a char filter matches a character via binary search
|
||||||
|
#[must_use]
|
||||||
pub fn char_filter_match(cf: &(impl ICFilter + ?Sized), c: char) -> bool {
|
pub fn char_filter_match(cf: &(impl ICFilter + ?Sized), c: char) -> bool {
|
||||||
match cf.ranges().binary_search_by_key(&c, |l| *l.end()) {
|
match cf.ranges().binary_search_by_key(&c, |l| *l.end()) {
|
||||||
Ok(_) => true, // c is the end of a range
|
Ok(_) => true, // c is the end of a range
|
||||||
@@ -48,6 +53,7 @@ pub fn char_filter_match(cf: &(impl ICFilter + ?Sized), c: char) -> bool {
|
|||||||
|
|
||||||
/// Merge two char filters into a filter that matches if either of the
|
/// Merge two char filters into a filter that matches if either of the
|
||||||
/// constituents would match.
|
/// constituents would match.
|
||||||
|
#[must_use]
|
||||||
pub fn char_filter_union(
|
pub fn char_filter_union(
|
||||||
l: &(impl ICFilter + ?Sized),
|
l: &(impl ICFilter + ?Sized),
|
||||||
r: &(impl ICFilter + ?Sized),
|
r: &(impl ICFilter + ?Sized),
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
//! The concept of a fallible merger
|
|
||||||
|
|
||||||
use never::Never;
|
|
||||||
|
|
||||||
/// Fallible, type-preserving variant of [std::ops::Add] implemented by a
|
|
||||||
/// variety of types for different purposes. Very broadly, if the operation
|
|
||||||
/// succeeds, the result should represent _both_ inputs.
|
|
||||||
pub trait Combine: Sized {
|
|
||||||
/// Information about the failure
|
|
||||||
type Error;
|
|
||||||
|
|
||||||
/// Merge two values into a value that represents both, if this is possible.
|
|
||||||
fn combine(self, other: Self) -> Result<Self, Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Combine for Never {
|
|
||||||
type Error = Never;
|
|
||||||
fn combine(self, _: Self) -> Result<Self, Self::Error> { match self {} }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Combine for () {
|
|
||||||
type Error = Never;
|
|
||||||
fn combine(self, (): Self) -> Result<Self, Self::Error> { Ok(()) }
|
|
||||||
}
|
|
||||||
582
orchid-base/src/comm.rs
Normal file
582
orchid-base/src/comm.rs
Normal file
@@ -0,0 +1,582 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::pin::{Pin, pin};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::{io, mem};
|
||||||
|
|
||||||
|
use async_fn_stream::try_stream;
|
||||||
|
use bound::Bound;
|
||||||
|
use derive_destructure::destructure;
|
||||||
|
use futures::channel::mpsc::{self, Receiver, Sender, channel};
|
||||||
|
use futures::channel::oneshot;
|
||||||
|
use futures::future::LocalBoxFuture;
|
||||||
|
use futures::lock::{Mutex, MutexGuard};
|
||||||
|
use futures::{
|
||||||
|
AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt, Stream, StreamExt, stream_select,
|
||||||
|
};
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use orchid_api_traits::{Decode, Encode, Request, UnderRoot};
|
||||||
|
use orchid_async_utils::LocalSet;
|
||||||
|
use orchid_async_utils::debug::{PanicOnDrop, assert_no_drop};
|
||||||
|
|
||||||
|
#[must_use = "Receipts indicate that a required action has been performed within a function. \
|
||||||
|
Most likely this should be returned somewhere."]
|
||||||
|
pub struct Receipt<'a>(PhantomData<&'a mut ()>);
|
||||||
|
impl Receipt<'_> {
|
||||||
|
/// Only call this function from a custom implementation of [RepWriter]
|
||||||
|
pub fn _new() -> Self { Self(PhantomData) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write guard to outbound for the purpose of serializing a request. Only one
|
||||||
|
/// can exist at a time. Dropping this object should panic.
|
||||||
|
pub trait ReqWriter<'a> {
|
||||||
|
/// Access to the underlying channel. This may be buffered.
|
||||||
|
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
|
||||||
|
/// Finalize the request, release the outbound channel, then queue for the
|
||||||
|
/// reply on the inbound channel.
|
||||||
|
fn send(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepReader<'a> + 'a>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write guard to inbound for the purpose of deserializing a reply. While held,
|
||||||
|
/// no inbound requests or other replies can be processed.
|
||||||
|
///
|
||||||
|
/// Dropping this object should panic even if [RepReader::finish] returns
|
||||||
|
/// synchronously, because the API isn't cancellation safe in general so it is a
|
||||||
|
/// programmer error in all cases to drop an object related to it without proper
|
||||||
|
/// cleanup.
|
||||||
|
pub trait RepReader<'a> {
|
||||||
|
/// Access to the underlying channel. The length of the message is inferred
|
||||||
|
/// from the number of bytes read so this must not be buffered.
|
||||||
|
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>;
|
||||||
|
/// Finish reading the request
|
||||||
|
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, ()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write guard to outbound for the purpose of serializing a notification.
|
||||||
|
///
|
||||||
|
/// Dropping this object should panic for the same reason [RepReader] panics
|
||||||
|
pub trait MsgWriter<'a> {
|
||||||
|
/// Access to the underlying channel. This may be buffered.
|
||||||
|
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
|
||||||
|
/// Send the notification
|
||||||
|
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<()>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For initiating outbound requests and notifications
|
||||||
|
pub trait Client {
|
||||||
|
fn start_request(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn ReqWriter<'_> + '_>>>;
|
||||||
|
fn start_notif(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn MsgWriter<'_> + '_>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Client + ?Sized> ClientExt for T {}
|
||||||
|
/// Extension trait with convenience methods that handle outbound request and
|
||||||
|
/// notif lifecycle and typing
|
||||||
|
#[allow(async_fn_in_trait)]
|
||||||
|
pub trait ClientExt: Client {
|
||||||
|
async fn request<T: Request + UnderRoot<Root: Encode>>(&self, t: T) -> io::Result<T::Response> {
|
||||||
|
let mut req = self.start_request().await?;
|
||||||
|
t.into_root().encode(req.writer().as_mut()).await?;
|
||||||
|
let mut rep = req.send().await?;
|
||||||
|
let response = T::Response::decode(rep.reader()).await;
|
||||||
|
rep.finish().await;
|
||||||
|
response
|
||||||
|
}
|
||||||
|
async fn notify<T: UnderRoot<Root: Encode>>(&self, t: T) -> io::Result<()> {
|
||||||
|
let mut notif = self.start_notif().await?;
|
||||||
|
t.into_root().encode(notif.writer().as_mut()).await?;
|
||||||
|
notif.finish().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ReqReader<'a> {
|
||||||
|
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>;
|
||||||
|
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, Box<dyn ReqHandle<'a> + 'a>>;
|
||||||
|
}
|
||||||
|
impl<'a, T: ReqReader<'a> + ?Sized> ReqReaderExt<'a> for T {}
|
||||||
|
#[allow(async_fn_in_trait)]
|
||||||
|
pub trait ReqReaderExt<'a>: ReqReader<'a> {
|
||||||
|
async fn read_req<R: Decode>(&mut self) -> io::Result<R> { R::decode(self.reader()).await }
|
||||||
|
async fn reply<R: Request>(
|
||||||
|
self: Box<Self>,
|
||||||
|
req: impl Evidence<R>,
|
||||||
|
rep: &R::Response,
|
||||||
|
) -> io::Result<Receipt<'a>> {
|
||||||
|
self.finish().await.reply(req, rep).await
|
||||||
|
}
|
||||||
|
async fn start_reply(self: Box<Self>) -> io::Result<Box<dyn RepWriter<'a> + 'a>> {
|
||||||
|
self.finish().await.start_reply().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ReqHandle<'a> {
|
||||||
|
fn start_reply(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepWriter<'a> + 'a>>>;
|
||||||
|
}
|
||||||
|
impl<'a, T: ReqHandle<'a> + ?Sized> ReqHandleExt<'a> for T {}
|
||||||
|
#[allow(async_fn_in_trait)]
|
||||||
|
pub trait ReqHandleExt<'a>: ReqHandle<'a> {
|
||||||
|
async fn reply<Req: Request>(
|
||||||
|
self: Box<Self>,
|
||||||
|
_: impl Evidence<Req>,
|
||||||
|
rep: &Req::Response,
|
||||||
|
) -> io::Result<Receipt<'a>> {
|
||||||
|
let mut reply = self.start_reply().await?;
|
||||||
|
rep.encode(reply.writer()).await?;
|
||||||
|
reply.finish().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RepWriter<'a> {
|
||||||
|
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite>;
|
||||||
|
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Receipt<'a>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait MsgReader<'a> {
|
||||||
|
fn reader(&mut self) -> Pin<&mut dyn AsyncRead>;
|
||||||
|
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, ()>;
|
||||||
|
}
|
||||||
|
impl<'a, T: ?Sized + MsgReader<'a>> MsgReaderExt<'a> for T {}
|
||||||
|
#[allow(async_fn_in_trait)]
|
||||||
|
pub trait MsgReaderExt<'a>: MsgReader<'a> {
|
||||||
|
async fn read<N: Decode>(mut self: Box<Self>) -> io::Result<N> {
|
||||||
|
let n = N::decode(self.reader()).await;
|
||||||
|
self.finish().await;
|
||||||
|
n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A form of [Evidence] that doesn't require the value to be kept around
|
||||||
|
pub struct Witness<T>(PhantomData<T>);
|
||||||
|
impl<T> Witness<T> {
|
||||||
|
pub fn of(_: &T) -> Self { Self(PhantomData) }
|
||||||
|
}
|
||||||
|
impl<T> Copy for Witness<T> {}
|
||||||
|
impl<T> Clone for Witness<T> {
|
||||||
|
fn clone(&self) -> Self { *self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A proxy for the type of a value either previously saved into a [Witness] or
|
||||||
|
/// still available.
|
||||||
|
pub trait Evidence<T> {}
|
||||||
|
impl<T> Evidence<T> for &'_ T {}
|
||||||
|
impl<T> Evidence<T> for Witness<T> {}
|
||||||
|
|
||||||
|
type IoRef<T> = Pin<Box<T>>;
|
||||||
|
type IoLock<T> = Rc<Mutex<Pin<Box<T>>>>;
|
||||||
|
type IoGuard<T> = Bound<MutexGuard<'static, Pin<Box<T>>>, IoLock<T>>;
|
||||||
|
|
||||||
|
/// An incoming request. This holds a lock on the ingress channel.
|
||||||
|
pub struct IoReqReader<'a> {
|
||||||
|
prefix: &'a [u8],
|
||||||
|
read: IoGuard<dyn AsyncRead>,
|
||||||
|
write: &'a Mutex<IoRef<dyn AsyncWrite>>,
|
||||||
|
}
|
||||||
|
impl<'a> ReqReader<'a> for IoReqReader<'a> {
|
||||||
|
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
|
||||||
|
fn finish(self: Box<Self>) -> LocalBoxFuture<'a, Box<dyn ReqHandle<'a> + 'a>> {
|
||||||
|
Box::pin(async {
|
||||||
|
Box::new(IoReqHandle { prefix: self.prefix, write: self.write }) as Box<dyn ReqHandle<'a>>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct IoReqHandle<'a> {
|
||||||
|
prefix: &'a [u8],
|
||||||
|
write: &'a Mutex<IoRef<dyn AsyncWrite>>,
|
||||||
|
}
|
||||||
|
impl<'a> ReqHandle<'a> for IoReqHandle<'a> {
|
||||||
|
fn start_reply(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepWriter<'a> + 'a>>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let mut write = self.write.lock().await;
|
||||||
|
write.as_mut().write_all(self.prefix).await?;
|
||||||
|
Ok(Box::new(IoRepWriter { write }) as Box<dyn RepWriter<'a>>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct IoRepWriter<'a> {
|
||||||
|
write: MutexGuard<'a, IoRef<dyn AsyncWrite>>,
|
||||||
|
}
|
||||||
|
impl<'a> RepWriter<'a> for IoRepWriter<'a> {
|
||||||
|
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.write.as_mut() }
|
||||||
|
fn finish(mut self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Receipt<'a>>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
self.writer().flush().await?;
|
||||||
|
Ok(Receipt(PhantomData))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IoMsgReader<'a> {
|
||||||
|
_pd: PhantomData<&'a mut ()>,
|
||||||
|
read: IoGuard<dyn AsyncRead>,
|
||||||
|
}
|
||||||
|
impl<'a> MsgReader<'a> for IoMsgReader<'a> {
|
||||||
|
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.read.as_mut() }
|
||||||
|
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> { Box::pin(async {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ReplySub {
|
||||||
|
id: u64,
|
||||||
|
ack: oneshot::Sender<()>,
|
||||||
|
cb: oneshot::Sender<IoGuard<dyn AsyncRead>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IoClient {
|
||||||
|
output: IoLock<dyn AsyncWrite>,
|
||||||
|
id: Rc<RefCell<u64>>,
|
||||||
|
subscribe: Rc<Sender<ReplySub>>,
|
||||||
|
}
|
||||||
|
impl IoClient {
|
||||||
|
fn new(output: IoLock<dyn AsyncWrite>) -> (Receiver<ReplySub>, Self) {
|
||||||
|
let (req, rep) = mpsc::channel(0);
|
||||||
|
(rep, Self { output, id: Rc::new(RefCell::new(0)), subscribe: Rc::new(req) })
|
||||||
|
}
|
||||||
|
async fn lock_out(&self) -> IoGuard<dyn AsyncWrite> {
|
||||||
|
Bound::async_new(self.output.clone(), async |o| o.lock().await).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Client for IoClient {
|
||||||
|
fn start_notif(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn MsgWriter<'_> + '_>>> {
|
||||||
|
Box::pin(async {
|
||||||
|
let drop_g = assert_no_drop("Notif future dropped");
|
||||||
|
let mut o = self.lock_out().await;
|
||||||
|
0u64.encode(o.as_mut()).await?;
|
||||||
|
drop_g.defuse();
|
||||||
|
Ok(Box::new(IoNotifWriter { o, drop_g: assert_no_drop("Notif writer dropped") })
|
||||||
|
as Box<dyn MsgWriter>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn start_request(&self) -> LocalBoxFuture<'_, io::Result<Box<dyn ReqWriter<'_> + '_>>> {
|
||||||
|
Box::pin(async {
|
||||||
|
let id = {
|
||||||
|
let mut id_g = self.id.borrow_mut();
|
||||||
|
*id_g += 1;
|
||||||
|
*id_g
|
||||||
|
};
|
||||||
|
let (cb, reply) = oneshot::channel();
|
||||||
|
let (ack, got_ack) = oneshot::channel();
|
||||||
|
let drop_g = assert_no_drop("Request future dropped");
|
||||||
|
self.subscribe.as_ref().clone().send(ReplySub { id, ack, cb }).await.unwrap();
|
||||||
|
got_ack.await.unwrap();
|
||||||
|
let mut w = self.lock_out().await;
|
||||||
|
id.encode(w.as_mut()).await?;
|
||||||
|
drop_g.defuse();
|
||||||
|
Ok(Box::new(IoReqWriter {
|
||||||
|
reply,
|
||||||
|
w,
|
||||||
|
drop_g: assert_no_drop("Request reader dropped without reply"),
|
||||||
|
}) as Box<dyn ReqWriter>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IoReqWriter {
|
||||||
|
reply: oneshot::Receiver<IoGuard<dyn AsyncRead>>,
|
||||||
|
w: IoGuard<dyn AsyncWrite>,
|
||||||
|
drop_g: PanicOnDrop,
|
||||||
|
}
|
||||||
|
impl<'a> ReqWriter<'a> for IoReqWriter {
|
||||||
|
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.w.as_mut() }
|
||||||
|
fn send(self: Box<Self>) -> LocalBoxFuture<'a, io::Result<Box<dyn RepReader<'a> + 'a>>> {
|
||||||
|
Box::pin(async {
|
||||||
|
let Self { reply, mut w, drop_g } = *self;
|
||||||
|
w.flush().await?;
|
||||||
|
mem::drop(w);
|
||||||
|
let i = reply.await.expect("Client dropped before reply received");
|
||||||
|
drop_g.defuse();
|
||||||
|
Ok(Box::new(IoRepReader {
|
||||||
|
i,
|
||||||
|
drop_g: assert_no_drop("Reply reader dropped without finishing"),
|
||||||
|
}) as Box<dyn RepReader>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IoRepReader {
|
||||||
|
i: IoGuard<dyn AsyncRead>,
|
||||||
|
drop_g: PanicOnDrop,
|
||||||
|
}
|
||||||
|
impl<'a> RepReader<'a> for IoRepReader {
|
||||||
|
fn reader(&mut self) -> Pin<&mut dyn AsyncRead> { self.i.as_mut() }
|
||||||
|
fn finish(self: Box<Self>) -> LocalBoxFuture<'static, ()> {
|
||||||
|
Box::pin(async { self.drop_g.defuse() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(destructure)]
|
||||||
|
struct IoNotifWriter {
|
||||||
|
o: IoGuard<dyn AsyncWrite>,
|
||||||
|
drop_g: PanicOnDrop,
|
||||||
|
}
|
||||||
|
impl<'a> MsgWriter<'a> for IoNotifWriter {
|
||||||
|
fn writer(&mut self) -> Pin<&mut dyn AsyncWrite> { self.o.as_mut() }
|
||||||
|
fn finish(mut self: Box<Self>) -> LocalBoxFuture<'static, io::Result<()>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let ret = self.o.flush().await;
|
||||||
|
self.drop_g.defuse();
|
||||||
|
ret
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CommCtx {
|
||||||
|
exit: Sender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommCtx {
|
||||||
|
pub async fn exit(self) -> io::Result<()> {
|
||||||
|
self.exit.clone().send(()).await.expect("quit channel dropped");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establish bidirectional request-notification communication over a duplex
|
||||||
|
/// channel. The returned [IoClient] can be used for notifications immediately,
|
||||||
|
/// but requests can only be received while the future is running. The future
|
||||||
|
/// will only resolve when [CommCtx::quit] is called. The generic type
|
||||||
|
/// parameters are associated with the client and serve to ensure with a runtime
|
||||||
|
/// check that the correct message families are sent in the correct directions
|
||||||
|
/// across the channel.
|
||||||
|
pub fn io_comm(
|
||||||
|
o: Pin<Box<dyn AsyncWrite>>,
|
||||||
|
i: Pin<Box<dyn AsyncRead>>,
|
||||||
|
) -> (impl Client + 'static, CommCtx, IoCommServer) {
|
||||||
|
let i = Rc::new(Mutex::new(i));
|
||||||
|
let o = Rc::new(Mutex::new(o));
|
||||||
|
let (onsub, client) = IoClient::new(o.clone());
|
||||||
|
let (exit, onexit) = channel(1);
|
||||||
|
(client, CommCtx { exit }, IoCommServer { o, i, onsub, onexit })
|
||||||
|
}
|
||||||
|
pub struct IoCommServer {
|
||||||
|
o: Rc<Mutex<Pin<Box<dyn AsyncWrite>>>>,
|
||||||
|
i: Rc<Mutex<Pin<Box<dyn AsyncRead>>>>,
|
||||||
|
onsub: Receiver<ReplySub>,
|
||||||
|
onexit: Receiver<()>,
|
||||||
|
}
|
||||||
|
impl IoCommServer {
|
||||||
|
pub async fn listen(
|
||||||
|
self,
|
||||||
|
notif: impl for<'a> AsyncFn(Box<dyn MsgReader<'a> + 'a>) -> io::Result<()>,
|
||||||
|
req: impl for<'a> AsyncFn(Box<dyn ReqReader<'a> + 'a>) -> io::Result<Receipt<'a>>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
let Self { o, i, onexit, onsub } = self;
|
||||||
|
enum Event {
|
||||||
|
Input(u64, IoGuard<dyn AsyncRead>),
|
||||||
|
Sub(ReplySub),
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
let input_stream = try_stream(async |mut h| {
|
||||||
|
loop {
|
||||||
|
let mut g = Bound::async_new(i.clone(), async |i| i.lock().await).await;
|
||||||
|
match u64::decode(g.as_mut()).await {
|
||||||
|
Ok(id) => h.emit(Event::Input(id, g)).await,
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
io::ErrorKind::BrokenPipe
|
||||||
|
| io::ErrorKind::ConnectionAborted
|
||||||
|
| io::ErrorKind::UnexpectedEof => h.emit(Event::Exit).await,
|
||||||
|
_ => return Err(e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let (mut add_pending_req, fork_future) = LocalSet::new();
|
||||||
|
let mut fork_stream = pin!(fork_future.into_stream());
|
||||||
|
let mut pending_replies = HashMap::new();
|
||||||
|
'body: {
|
||||||
|
let mut shared = stream_select! {
|
||||||
|
pin!(input_stream) as Pin<&mut dyn Stream<Item = io::Result<Event>>>,
|
||||||
|
onsub.map(|sub| Ok(Event::Sub(sub))),
|
||||||
|
fork_stream.as_mut().map(|res| {
|
||||||
|
res.map(|()| panic!("this substream cannot exit while the loop is running") as Event)
|
||||||
|
}),
|
||||||
|
onexit.map(|()| Ok(Event::Exit)),
|
||||||
|
};
|
||||||
|
while let Some(next) = shared.next().await {
|
||||||
|
match next {
|
||||||
|
Err(e) => break 'body Err(e),
|
||||||
|
Ok(Event::Exit) => break,
|
||||||
|
Ok(Event::Sub(ReplySub { id, ack, cb })) => {
|
||||||
|
pending_replies.insert(id, cb);
|
||||||
|
// this is detected and logged on client
|
||||||
|
let _ = ack.send(());
|
||||||
|
},
|
||||||
|
Ok(Event::Input(0, read)) => {
|
||||||
|
let notif = ¬if;
|
||||||
|
let notif_job =
|
||||||
|
async move { notif(Box::new(IoMsgReader { _pd: PhantomData, read })).await };
|
||||||
|
add_pending_req.send(Box::pin(notif_job)).await.unwrap();
|
||||||
|
},
|
||||||
|
// MSB == 0 is a request, !id where MSB == 1 is the corresponding response
|
||||||
|
Ok(Event::Input(id, read)) if (id & (1 << (u64::BITS - 1))) == 0 => {
|
||||||
|
let (o, req) = (o.clone(), &req);
|
||||||
|
let req_job = async move {
|
||||||
|
let mut prefix = Vec::new();
|
||||||
|
(!id).encode_vec(&mut prefix);
|
||||||
|
let _ = req(Box::new(IoReqReader { prefix: &pin!(prefix), read, write: &o })).await;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
add_pending_req.send(Box::pin(req_job)).await.unwrap();
|
||||||
|
},
|
||||||
|
Ok(Event::Input(id, read)) => {
|
||||||
|
let cb = pending_replies.remove(&!id).expect("Reply to unrecognized request");
|
||||||
|
cb.send(read).unwrap_or_else(|_| panic!("Failed to send reply"));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}?;
|
||||||
|
mem::drop(add_pending_req);
|
||||||
|
while let Some(next) = fork_stream.next().await {
|
||||||
|
next?
|
||||||
|
}
|
||||||
|
let mut out = o.lock().await;
|
||||||
|
out.as_mut().flush().await?;
|
||||||
|
out.as_mut().close().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use futures::channel::mpsc;
|
||||||
|
use futures::{SinkExt, StreamExt, join};
|
||||||
|
use orchid_api_derive::{Coding, Hierarchy};
|
||||||
|
use orchid_api_traits::Request;
|
||||||
|
use orchid_async_utils::debug::spin_on;
|
||||||
|
use unsync_pipe::pipe;
|
||||||
|
|
||||||
|
use crate::comm::{ClientExt, MsgReaderExt, ReqReaderExt, io_comm};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Coding, Hierarchy)]
|
||||||
|
#[extendable]
|
||||||
|
struct TestNotif(u64);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn notification() {
|
||||||
|
spin_on(async {
|
||||||
|
let (in1, out2) = pipe(1024);
|
||||||
|
let (in2, out1) = pipe(1024);
|
||||||
|
let (received, mut on_receive) = mpsc::channel(2);
|
||||||
|
let (_, recv_ctx, recv_srv) = io_comm(Box::pin(in2), Box::pin(out2));
|
||||||
|
let (sender, ..) = io_comm(Box::pin(in1), Box::pin(out1));
|
||||||
|
join!(
|
||||||
|
async {
|
||||||
|
recv_srv
|
||||||
|
.listen(
|
||||||
|
async |notif| {
|
||||||
|
received.clone().send(notif.read::<TestNotif>().await?).await.unwrap();
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
async |_| panic!("Should receive notif, not request"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
},
|
||||||
|
async {
|
||||||
|
sender.notify(TestNotif(3)).await.unwrap();
|
||||||
|
assert_eq!(on_receive.next().await, Some(TestNotif(3)));
|
||||||
|
sender.notify(TestNotif(4)).await.unwrap();
|
||||||
|
assert_eq!(on_receive.next().await, Some(TestNotif(4)));
|
||||||
|
recv_ctx.exit().await.unwrap();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Coding, Hierarchy)]
|
||||||
|
#[extendable]
|
||||||
|
struct DummyRequest(u64);
|
||||||
|
impl Request for DummyRequest {
|
||||||
|
type Response = u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn request() {
|
||||||
|
spin_on(async {
|
||||||
|
let (in1, out2) = pipe(1024);
|
||||||
|
let (in2, out1) = pipe(1024);
|
||||||
|
let (_, srv_ctx, srv) = io_comm(Box::pin(in2), Box::pin(out2));
|
||||||
|
let (client, client_ctx, client_srv) = io_comm(Box::pin(in1), Box::pin(out1));
|
||||||
|
join!(
|
||||||
|
async {
|
||||||
|
srv
|
||||||
|
.listen(
|
||||||
|
async |_| panic!("No notifs expected"),
|
||||||
|
async |mut req| {
|
||||||
|
let val = req.read_req::<DummyRequest>().await?;
|
||||||
|
req.reply(&val, &(val.0 + 1)).await
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
},
|
||||||
|
async {
|
||||||
|
client_srv
|
||||||
|
.listen(
|
||||||
|
async |_| panic!("Not expecting ingress notif"),
|
||||||
|
async |_| panic!("Not expecting ingress req"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
},
|
||||||
|
async {
|
||||||
|
let response = client.request(DummyRequest(5)).await.unwrap();
|
||||||
|
assert_eq!(response, 6);
|
||||||
|
srv_ctx.exit().await.unwrap();
|
||||||
|
client_ctx.exit().await.unwrap();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exit() {
|
||||||
|
spin_on(async {
|
||||||
|
let (input1, output1) = pipe(1024);
|
||||||
|
let (input2, output2) = pipe(1024);
|
||||||
|
let (reply_client, reply_context, reply_server) =
|
||||||
|
io_comm(Box::pin(input1), Box::pin(output2));
|
||||||
|
let (req_client, req_context, req_server) = io_comm(Box::pin(input2), Box::pin(output1));
|
||||||
|
let reply_context = RefCell::new(Some(reply_context));
|
||||||
|
let (exit, onexit) = futures::channel::oneshot::channel::<()>();
|
||||||
|
join!(
|
||||||
|
async move {
|
||||||
|
reply_server
|
||||||
|
.listen(
|
||||||
|
async |hand| {
|
||||||
|
let _notif = hand.read::<TestNotif>().await.unwrap();
|
||||||
|
let context = reply_context.borrow_mut().take().unwrap();
|
||||||
|
context.exit().await?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
async |mut hand| {
|
||||||
|
let req = hand.read_req::<DummyRequest>().await?;
|
||||||
|
hand.reply(&req, &(req.0 + 1)).await
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
exit.send(()).unwrap();
|
||||||
|
let _client = reply_client;
|
||||||
|
},
|
||||||
|
async move {
|
||||||
|
req_server
|
||||||
|
.listen(
|
||||||
|
async |_| panic!("Only the other server expected notifs"),
|
||||||
|
async |_| panic!("Only the other server expected requests"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let _ctx = req_context;
|
||||||
|
},
|
||||||
|
async move {
|
||||||
|
req_client.request(DummyRequest(0)).await.unwrap();
|
||||||
|
req_client.notify(TestNotif(0)).await.unwrap();
|
||||||
|
onexit.await.unwrap();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::num::{NonZero, NonZeroUsize};
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use futures::FutureExt;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use task_local::task_local;
|
||||||
|
|
||||||
use crate::api;
|
use crate::{IStr, Pos, api, es, is};
|
||||||
use crate::interner::{Interner, Tok};
|
|
||||||
use crate::location::Pos;
|
|
||||||
|
|
||||||
/// A point of interest in resolving the error, such as the point where
|
/// A point of interest in resolving the error, such as the point where
|
||||||
/// processing got stuck, a command that is likely to be incorrect
|
/// processing got stuck, a command that is likely to be incorrect
|
||||||
@@ -21,13 +23,16 @@ pub struct ErrPos {
|
|||||||
pub message: Option<Arc<String>>,
|
pub message: Option<Arc<String>>,
|
||||||
}
|
}
|
||||||
impl ErrPos {
|
impl ErrPos {
|
||||||
|
/// Create from a position with a position-specific message. If there's no
|
||||||
|
/// message, use `Pos::into`
|
||||||
|
#[must_use]
|
||||||
pub fn new(msg: &str, position: Pos) -> Self {
|
pub fn new(msg: &str, position: Pos) -> Self {
|
||||||
Self { message: Some(Arc::new(msg.to_string())), position }
|
Self { message: Some(Arc::new(msg.to_string())), position }
|
||||||
}
|
}
|
||||||
async fn from_api(api: &api::ErrLocation, i: &Interner) -> Self {
|
async fn from_api(api: api::ErrLocation) -> Self {
|
||||||
Self {
|
Self {
|
||||||
message: Some(api.message.clone()).filter(|s| !s.is_empty()),
|
message: Some(api.message).filter(|s| !s.is_empty()),
|
||||||
position: Pos::from_api(&api.location, i).await,
|
position: Pos::from_api(&api.location).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn to_api(&self) -> api::ErrLocation {
|
fn to_api(&self) -> api::ErrLocation {
|
||||||
@@ -49,10 +54,16 @@ impl fmt::Display for ErrPos {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error that occurred in Orchid code, whether at startup or runtime
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OrcErr {
|
pub struct OrcErr {
|
||||||
pub description: Tok<String>,
|
/// A generic error message used in categorizing errors
|
||||||
pub message: Arc<String>,
|
/// You can also equality-compare atoms with these message tokens
|
||||||
|
pub description: IStr,
|
||||||
|
/// A specific error message that may include values relevant in resolving the
|
||||||
|
/// error
|
||||||
|
pub message: Rc<String>,
|
||||||
|
/// Various locations in code that may be useful in resolving the error
|
||||||
pub positions: Vec<ErrPos>,
|
pub positions: Vec<ErrPos>,
|
||||||
}
|
}
|
||||||
impl OrcErr {
|
impl OrcErr {
|
||||||
@@ -63,16 +74,16 @@ impl OrcErr {
|
|||||||
locations: self.positions.iter().map(ErrPos::to_api).collect(),
|
locations: self.positions.iter().map(ErrPos::to_api).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn from_api(api: &api::OrcError, i: &Interner) -> Self {
|
async fn from_api(api: api::OrcError) -> Self {
|
||||||
Self {
|
Self {
|
||||||
description: Tok::from_api(api.description, i).await,
|
description: es(api.description).await,
|
||||||
message: api.message.clone(),
|
message: api.message,
|
||||||
positions: join_all(api.locations.iter().map(|e| ErrPos::from_api(e, i))).await,
|
positions: join_all(api.locations.into_iter().map(ErrPos::from_api)).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PartialEq<Tok<String>> for OrcErr {
|
impl PartialEq<IStr> for OrcErr {
|
||||||
fn eq(&self, other: &Tok<String>) -> bool { self.description == *other }
|
fn eq(&self, other: &IStr) -> bool { self.description == *other }
|
||||||
}
|
}
|
||||||
impl From<OrcErr> for Vec<OrcErr> {
|
impl From<OrcErr> for Vec<OrcErr> {
|
||||||
fn from(value: OrcErr) -> Self { vec![value] }
|
fn from(value: OrcErr) -> Self { vec![value] }
|
||||||
@@ -84,6 +95,8 @@ impl fmt::Display for OrcErr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rust error produced when an Orchid error condition arises but no
|
||||||
|
/// specific errors are listed. This is always a Rust programmer error.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct EmptyErrv;
|
pub struct EmptyErrv;
|
||||||
impl fmt::Display for EmptyErrv {
|
impl fmt::Display for EmptyErrv {
|
||||||
@@ -92,42 +105,58 @@ impl fmt::Display for EmptyErrv {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A container for one or more errors. Code that supports error recovery should
|
||||||
|
/// use these instead of plain [OrcErr] objects.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OrcErrv(Vec<OrcErr>);
|
pub struct OrcErrv(Vec<OrcErr>);
|
||||||
impl OrcErrv {
|
impl OrcErrv {
|
||||||
|
/// Create from individual errors. If you have exactly one initial error, see
|
||||||
|
/// [mk_errv]
|
||||||
pub fn new(errors: impl IntoIterator<Item = OrcErr>) -> Result<Self, EmptyErrv> {
|
pub fn new(errors: impl IntoIterator<Item = OrcErr>) -> Result<Self, EmptyErrv> {
|
||||||
let v = errors.into_iter().collect_vec();
|
let v = errors.into_iter().collect_vec();
|
||||||
if v.is_empty() { Err(EmptyErrv) } else { Ok(Self(v)) }
|
if v.is_empty() { Err(EmptyErrv) } else { Ok(Self(v)) }
|
||||||
}
|
}
|
||||||
|
/// Add additional errors to this container. Since `OrcErrv` also implements
|
||||||
|
/// [IntoIterator], this can take `(Self, Self)`
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn extended<T>(mut self, errors: impl IntoIterator<Item = T>) -> Self
|
pub fn extended<T>(mut self, errors: impl IntoIterator<Item = T>) -> Self
|
||||||
where Self: Extend<T> {
|
where Self: Extend<T> {
|
||||||
self.extend(errors);
|
self.extend(errors);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Determine how many distinct errors there are in the container
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn len(&self) -> usize { self.0.len() }
|
pub fn len(&self) -> NonZeroUsize { NonZero::new(self.0.len()).expect("OrcErrv cannot be empty") }
|
||||||
#[must_use]
|
/// See if any errors match a particular filter criteria. This is useful for
|
||||||
pub fn is_empty(&self) -> bool { self.len() == 0 }
|
/// sentinel errors whch are produced by user code to trigger unique
|
||||||
|
/// behaviours such as a lexer mismatch
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn any(&self, f: impl FnMut(&OrcErr) -> bool) -> bool { self.0.iter().any(f) }
|
pub fn any(&self, f: impl FnMut(&OrcErr) -> bool) -> bool { self.0.iter().any(f) }
|
||||||
|
/// Remove all errors that don't match a filter criterion. If no errors match,
|
||||||
|
/// nothing is returned
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn keep_only(self, f: impl FnMut(&OrcErr) -> bool) -> Option<Self> {
|
pub fn keep_only(self, f: impl FnMut(&OrcErr) -> bool) -> Option<Self> {
|
||||||
let v = self.0.into_iter().filter(f).collect_vec();
|
let v = self.0.into_iter().filter(f).collect_vec();
|
||||||
if v.is_empty() { None } else { Some(Self(v)) }
|
if v.is_empty() { None } else { Some(Self(v)) }
|
||||||
}
|
}
|
||||||
|
/// If there is exactly one error, return it. Mostly used for simplified
|
||||||
|
/// printing
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn one(&self) -> Option<&OrcErr> { (self.0.len() == 1).then(|| &self.0[9]) }
|
pub fn one(&self) -> Option<&OrcErr> { self.0.iter().exactly_one().ok() }
|
||||||
|
/// Iterate over all positions of all errors
|
||||||
pub fn pos_iter(&self) -> impl Iterator<Item = ErrPos> + '_ {
|
pub fn pos_iter(&self) -> impl Iterator<Item = ErrPos> + '_ {
|
||||||
self.0.iter().flat_map(|e| e.positions.iter().cloned())
|
self.0.iter().flat_map(|e| e.positions.iter().cloned())
|
||||||
}
|
}
|
||||||
|
/// Serialize for transmission
|
||||||
|
#[must_use]
|
||||||
pub fn to_api(&self) -> Vec<api::OrcError> { self.0.iter().map(OrcErr::to_api).collect() }
|
pub fn to_api(&self) -> Vec<api::OrcError> { self.0.iter().map(OrcErr::to_api).collect() }
|
||||||
pub async fn from_api<'a>(
|
/// Deserialize from transmission
|
||||||
api: impl IntoIterator<Item = &'a api::OrcError>,
|
#[must_use]
|
||||||
i: &Interner,
|
pub async fn from_api(api: impl IntoIterator<Item = api::OrcError>) -> Self {
|
||||||
) -> Self {
|
Self(join_all(api.into_iter().map(OrcErr::from_api)).await)
|
||||||
Self(join_all(api.into_iter().map(|e| OrcErr::from_api(e, i))).await)
|
|
||||||
}
|
}
|
||||||
|
/// Iterate over the errors without consuming the collection
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = OrcErr> + '_ { self.0.iter().cloned() }
|
||||||
}
|
}
|
||||||
impl From<OrcErr> for OrcErrv {
|
impl From<OrcErr> for OrcErrv {
|
||||||
fn from(value: OrcErr) -> Self { Self(vec![value]) }
|
fn from(value: OrcErr) -> Self { Self(vec![value]) }
|
||||||
@@ -155,8 +184,11 @@ impl fmt::Display for OrcErrv {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A result from a function that may return multiple errors.
|
||||||
pub type OrcRes<T> = Result<T, OrcErrv>;
|
pub type OrcRes<T> = Result<T, OrcErrv>;
|
||||||
|
|
||||||
|
/// If two fallible values both succeed return both values, otherwise return
|
||||||
|
/// all errors.
|
||||||
pub fn join_ok<T, U>(left: OrcRes<T>, right: OrcRes<U>) -> OrcRes<(T, U)> {
|
pub fn join_ok<T, U>(left: OrcRes<T>, right: OrcRes<U>) -> OrcRes<(T, U)> {
|
||||||
match (left, right) {
|
match (left, right) {
|
||||||
(Ok(t), Ok(u)) => Ok((t, u)),
|
(Ok(t), Ok(u)) => Ok((t, u)),
|
||||||
@@ -186,69 +218,109 @@ macro_rules! join_ok {
|
|||||||
};
|
};
|
||||||
(@TYPES) => { () };
|
(@TYPES) => { () };
|
||||||
(@VALUES $name:ident $(: $ty:ty)? = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => {
|
(@VALUES $name:ident $(: $ty:ty)? = $val:expr ; $($names:ident $(: $tys:ty)? = $vals:expr;)*) => {
|
||||||
$crate::error::join_ok($val, $crate::join_ok!(@VALUES $($names $(: $tys)? = $vals;)*))
|
$crate::join_ok($val, $crate::join_ok!(@VALUES $($names $(: $tys)? = $vals;)*))
|
||||||
};
|
};
|
||||||
(@VALUES) => { Ok(()) };
|
(@VALUES) => { Ok(()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mk_errv_floating(description: Tok<String>, message: impl AsRef<str>) -> OrcErrv {
|
/// Create an errv without an associated position, as opposed to [mk_errv].
|
||||||
|
/// While this is technically legal and sometimes needed in library code, all
|
||||||
|
/// errors that are technically possible to associate with at least one position
|
||||||
|
/// should be.
|
||||||
|
#[must_use]
|
||||||
|
pub fn mk_errv_floating(description: IStr, message: impl AsRef<str>) -> OrcErrv {
|
||||||
mk_errv::<Pos>(description, message, [])
|
mk_errv::<Pos>(description, message, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an errv. The third argument can be an iterable of [ErrPos] or [Pos].
|
||||||
|
#[must_use]
|
||||||
pub fn mk_errv<I: Into<ErrPos>>(
|
pub fn mk_errv<I: Into<ErrPos>>(
|
||||||
description: Tok<String>,
|
description: IStr,
|
||||||
message: impl AsRef<str>,
|
message: impl AsRef<str>,
|
||||||
posv: impl IntoIterator<Item = I>,
|
posv: impl IntoIterator<Item = I>,
|
||||||
) -> OrcErrv {
|
) -> OrcErrv {
|
||||||
OrcErr {
|
OrcErr {
|
||||||
description,
|
description,
|
||||||
message: Arc::new(message.as_ref().to_string()),
|
message: Rc::new(message.as_ref().to_string()),
|
||||||
positions: posv.into_iter().map_into().collect(),
|
positions: posv.into_iter().map_into().collect(),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a standard IO error into an Orchid error
|
||||||
|
#[must_use]
|
||||||
pub async fn async_io_err<I: Into<ErrPos>>(
|
pub async fn async_io_err<I: Into<ErrPos>>(
|
||||||
err: std::io::Error,
|
err: std::io::Error,
|
||||||
i: &Interner,
|
|
||||||
posv: impl IntoIterator<Item = I>,
|
posv: impl IntoIterator<Item = I>,
|
||||||
) -> OrcErrv {
|
) -> OrcErrv {
|
||||||
mk_errv(i.i(&err.kind().to_string()).await, err.to_string(), posv)
|
mk_errv(is(&err.kind().to_string()).await, err.to_string(), posv)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn os_str_to_string<'a, I: Into<ErrPos>>(
|
/// Decode an Unicode string, or produce a common error related to Unicode
|
||||||
str: &'a OsStr,
|
/// decoding
|
||||||
i: &Interner,
|
pub async fn os_str_to_string<I: Into<ErrPos>>(
|
||||||
|
str: &OsStr,
|
||||||
posv: impl IntoIterator<Item = I>,
|
posv: impl IntoIterator<Item = I>,
|
||||||
) -> OrcRes<&'a str> {
|
) -> OrcRes<&str> {
|
||||||
match str.to_str() {
|
match str.to_str() {
|
||||||
Some(str) => Ok(str),
|
Some(str) => Ok(str),
|
||||||
None => Err(mk_errv(
|
None => Err(mk_errv(
|
||||||
i.i("Non-unicode string").await,
|
is("Non-unicode string").await,
|
||||||
format!("{str:?} is not representable as unicode"),
|
format!("{str:?} is not representable as unicode"),
|
||||||
posv,
|
posv,
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Reporter {
|
#[derive(Clone, Default)]
|
||||||
errors: RefCell<Vec<OrcErr>>,
|
struct Reporter {
|
||||||
|
errors: Rc<RefCell<Vec<OrcErr>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Reporter {
|
task_local! {
|
||||||
pub fn report(&self, e: impl Into<OrcErrv>) { self.errors.borrow_mut().extend(e.into()) }
|
static REPORTER: Reporter;
|
||||||
pub fn new() -> Self { Self { errors: RefCell::new(vec![]) } }
|
|
||||||
pub fn errv(self) -> Option<OrcErrv> { OrcErrv::new(self.errors.into_inner()).ok() }
|
|
||||||
pub fn merge<T>(self, res: OrcRes<T>) -> OrcRes<T> {
|
|
||||||
match (res, self.errv()) {
|
|
||||||
(res, None) => res,
|
|
||||||
(Ok(_), Some(errv)) => Err(errv),
|
|
||||||
(Err(e), Some(errv)) => Err(e + errv),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn is_empty(&self) -> bool { self.errors.borrow().is_empty() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Reporter {
|
/// Run the future with a new reporter, and return all errors reported within.
|
||||||
fn default() -> Self { Self::new() }
|
///
|
||||||
|
/// If your future returns [OrcRes], see [try_with_reporter]
|
||||||
|
pub async fn with_reporter<T>(fut: impl Future<Output = T>) -> OrcRes<T> {
|
||||||
|
try_with_reporter(fut.map(Ok)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the future with a new reporter, and return all errors either returned or
|
||||||
|
/// reported by it
|
||||||
|
///
|
||||||
|
/// If your future may report errors but always returns an approximate value,
|
||||||
|
/// see [with_reporter]
|
||||||
|
pub async fn try_with_reporter<T>(fut: impl Future<Output = OrcRes<T>>) -> OrcRes<T> {
|
||||||
|
let rep = Reporter::default();
|
||||||
|
let res = REPORTER.scope(rep.clone(), fut).await;
|
||||||
|
let errors = rep.errors.take();
|
||||||
|
match (res, &errors[..]) {
|
||||||
|
(Ok(t), []) => Ok(t),
|
||||||
|
(Ok(_), [_, ..]) => Err(OrcErrv::new(errors).unwrap()),
|
||||||
|
(Err(e), _) => Err(e.extended(errors)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determine if there are pending errors or if this overarching procedure has a
|
||||||
|
/// chance to succeed
|
||||||
|
#[must_use]
|
||||||
|
pub async fn is_erroring() -> bool {
|
||||||
|
(REPORTER.try_with(|r| !r.errors.borrow().is_empty()))
|
||||||
|
.expect("Sidechannel errors must be caught by a reporter")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Report an error that is fatal and prevents a correct output, but
|
||||||
|
/// still allows the current task to continue and produce an approximate output.
|
||||||
|
/// This can be used for
|
||||||
|
pub fn report(e: impl Into<OrcErrv>) {
|
||||||
|
let errv = e.into();
|
||||||
|
REPORTER.try_with(|r| r.errors.borrow_mut().extend(errv.clone())).unwrap_or_else(|_| {
|
||||||
|
panic!(
|
||||||
|
"Unhandled error! Sidechannel errors must be caught by an enclosing call to with_reporter.\n\
|
||||||
|
Error: {errv}"
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
//! Multiple-listener-single-delivery event system.
|
|
||||||
|
|
||||||
use std::mem;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::sync::mpsc::{self, sync_channel};
|
|
||||||
|
|
||||||
struct Reply<T, U> {
|
|
||||||
resub: bool,
|
|
||||||
outcome: Result<U, T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Listener<T, E> {
|
|
||||||
sink: mpsc::SyncSender<T>,
|
|
||||||
source: mpsc::Receiver<Reply<T, E>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Event<T, U> {
|
|
||||||
listeners: Mutex<Vec<Listener<T, U>>>,
|
|
||||||
}
|
|
||||||
impl<T, U> Event<T, U> {
|
|
||||||
pub const fn new() -> Self { Self { listeners: Mutex::new(Vec::new()) } }
|
|
||||||
|
|
||||||
pub fn dispatch(&self, mut ev: T) -> Option<U> {
|
|
||||||
let mut listeners = self.listeners.lock().unwrap();
|
|
||||||
let mut alt_list = Vec::with_capacity(listeners.len());
|
|
||||||
mem::swap(&mut *listeners, &mut alt_list);
|
|
||||||
let mut items = alt_list.into_iter();
|
|
||||||
while let Some(l) = items.next() {
|
|
||||||
l.sink.send(ev).unwrap();
|
|
||||||
let Reply { resub, outcome } = l.source.recv().unwrap();
|
|
||||||
if resub {
|
|
||||||
listeners.push(l);
|
|
||||||
}
|
|
||||||
match outcome {
|
|
||||||
Ok(res) => {
|
|
||||||
listeners.extend(items);
|
|
||||||
return Some(res);
|
|
||||||
},
|
|
||||||
Err(next) => {
|
|
||||||
ev = next;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_one<V>(&self, mut filter: impl FnMut(&T) -> bool, f: impl FnOnce(T) -> (U, V)) -> V {
|
|
||||||
let mut listeners = self.listeners.lock().unwrap();
|
|
||||||
let (sink, request) = sync_channel(0);
|
|
||||||
let (response, source) = sync_channel(0);
|
|
||||||
listeners.push(Listener { sink, source });
|
|
||||||
mem::drop(listeners);
|
|
||||||
loop {
|
|
||||||
let t = request.recv().unwrap();
|
|
||||||
if filter(&t) {
|
|
||||||
let (u, v) = f(t);
|
|
||||||
response.send(Reply { resub: false, outcome: Ok(u) }).unwrap();
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
response.send(Reply { resub: true, outcome: Err(t) }).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> Default for Event<T, U> {
|
|
||||||
fn default() -> Self { Self::new() }
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ use std::cmp::Ordering;
|
|||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@@ -11,24 +12,29 @@ use itertools::{Itertools, chain};
|
|||||||
use never::Never;
|
use never::Never;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::interner::Interner;
|
|
||||||
use crate::{api, match_mapping};
|
use crate::{api, match_mapping};
|
||||||
|
|
||||||
|
/// A unit of formattable text where the formatter must make a single choice
|
||||||
|
/// Converting from various types via [Into::into] keeps strings intact, but
|
||||||
|
/// [str::parse] resolves escape sequences
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub struct FmtUnit {
|
pub struct FmtUnit {
|
||||||
|
/// Sub-units
|
||||||
pub subs: Vec<FmtUnit>,
|
pub subs: Vec<FmtUnit>,
|
||||||
|
/// Parsed text templates for how to render this text
|
||||||
pub variants: Rc<Variants>,
|
pub variants: Rc<Variants>,
|
||||||
}
|
}
|
||||||
impl FmtUnit {
|
impl FmtUnit {
|
||||||
pub fn new(variants: Rc<Variants>, subs: impl IntoIterator<Item = FmtUnit>) -> Self {
|
pub fn new(variants: Rc<Variants>, subs: impl IntoIterator<Item = FmtUnit>) -> Self {
|
||||||
Self { subs: subs.into_iter().collect(), variants }
|
Self { subs: subs.into_iter().collect(), variants }
|
||||||
}
|
}
|
||||||
|
/// Deserialize from message
|
||||||
pub fn from_api(api: &api::FormattingUnit) -> Self {
|
pub fn from_api(api: &api::FormattingUnit) -> Self {
|
||||||
Self {
|
Self {
|
||||||
subs: api.subs.iter().map(Self::from_api).collect(),
|
subs: api.subs.iter().map(Self::from_api).collect(),
|
||||||
variants: Rc::new(Variants(
|
variants: Rc::new(Variants(
|
||||||
(api.variants.iter().map(|var| Variant {
|
(api.variants.iter().map(|var| FmtVariant {
|
||||||
bounded: var.bounded,
|
bounded: var.bounded,
|
||||||
elements: var.elements.iter().map(FmtElement::from_api).collect(),
|
elements: var.elements.iter().map(FmtElement::from_api).collect(),
|
||||||
}))
|
}))
|
||||||
@@ -36,6 +42,8 @@ impl FmtUnit {
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Serialize into message. String interner IDs used in the structure must
|
||||||
|
/// remain valid.
|
||||||
pub fn to_api(&self) -> api::FormattingUnit {
|
pub fn to_api(&self) -> api::FormattingUnit {
|
||||||
api::FormattingUnit {
|
api::FormattingUnit {
|
||||||
subs: self.subs.iter().map(Self::to_api).collect(),
|
subs: self.subs.iter().map(Self::to_api).collect(),
|
||||||
@@ -46,11 +54,13 @@ impl FmtUnit {
|
|||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Shorthand for a variable-length list that can be formatted in exactly one
|
||||||
|
/// way
|
||||||
pub fn sequence(
|
pub fn sequence(
|
||||||
head: &str,
|
head: &str,
|
||||||
delim: &str,
|
delim: &str,
|
||||||
tail: &str,
|
tail: &str,
|
||||||
seq_bnd: Option<bool>,
|
seq_bnd: bool,
|
||||||
seq: impl IntoIterator<Item = FmtUnit>,
|
seq: impl IntoIterator<Item = FmtUnit>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let items = seq.into_iter().collect_vec();
|
let items = seq.into_iter().collect_vec();
|
||||||
@@ -69,18 +79,37 @@ impl FromStr for FmtUnit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single element of a format string. Composes into [FmtVariant]
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub enum FmtElement {
|
pub enum FmtElement {
|
||||||
Sub { slot: u32, bounded: Option<bool> },
|
/// a reference to an interpolable subunit in the enclosing [FmtUnit]
|
||||||
|
Sub {
|
||||||
|
/// Index into [FmtUnit::subs]
|
||||||
|
slot: u32,
|
||||||
|
/// Whether the subunit can use an unbounded (`Some(false)`) [FmtVariant],
|
||||||
|
/// it is restricted to bounded (`Some(true)`) [FmtVariant], or it should
|
||||||
|
/// inherit this information from the enclosing unit, meaning that the slot
|
||||||
|
/// is at the very end of the format string
|
||||||
|
bounded: Option<bool>,
|
||||||
|
},
|
||||||
|
/// a string snippet
|
||||||
String(Rc<String>),
|
String(Rc<String>),
|
||||||
|
/// an indented block
|
||||||
Indent(Vec<FmtElement>),
|
Indent(Vec<FmtElement>),
|
||||||
}
|
}
|
||||||
impl FmtElement {
|
impl FmtElement {
|
||||||
|
/// Create a plain string snippet
|
||||||
pub fn str(s: &'_ str) -> Self { Self::String(Rc::new(s.to_string())) }
|
pub fn str(s: &'_ str) -> Self { Self::String(Rc::new(s.to_string())) }
|
||||||
|
/// Create a slot for a subunit
|
||||||
pub fn sub(slot: u32, bounded: Option<bool>) -> Self { Self::Sub { slot, bounded } }
|
pub fn sub(slot: u32, bounded: Option<bool>) -> Self { Self::Sub { slot, bounded } }
|
||||||
|
/// Create a slot for a subunit's bounded representation
|
||||||
pub fn bounded(i: u32) -> Self { Self::sub(i, Some(true)) }
|
pub fn bounded(i: u32) -> Self { Self::sub(i, Some(true)) }
|
||||||
|
/// Create a slot for any representation of a subunit
|
||||||
pub fn unbounded(i: u32) -> Self { Self::sub(i, Some(false)) }
|
pub fn unbounded(i: u32) -> Self { Self::sub(i, Some(false)) }
|
||||||
|
/// Create an end slot bounded by the enclosing unit if that is bounded
|
||||||
pub fn last(i: u32) -> Self { Self::sub(i, None) }
|
pub fn last(i: u32) -> Self { Self::sub(i, None) }
|
||||||
|
/// Create a sequence of `len` unbounded slots capped by a slot of the
|
||||||
|
/// specified boundedness
|
||||||
pub fn sequence(len: usize, bounded: Option<bool>) -> Vec<Self> {
|
pub fn sequence(len: usize, bounded: Option<bool>) -> Vec<Self> {
|
||||||
match len.try_into().unwrap() {
|
match len.try_into().unwrap() {
|
||||||
0u32 => vec![],
|
0u32 => vec![],
|
||||||
@@ -88,6 +117,7 @@ impl FmtElement {
|
|||||||
n => (0..n - 1).map(FmtElement::unbounded).chain([FmtElement::sub(n - 1, bounded)]).collect(),
|
n => (0..n - 1).map(FmtElement::unbounded).chain([FmtElement::sub(n - 1, bounded)]).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Decode from a message
|
||||||
pub fn from_api(api: &api::FormattingElement) -> Self {
|
pub fn from_api(api: &api::FormattingElement) -> Self {
|
||||||
match_mapping!(api, api::FormattingElement => FmtElement {
|
match_mapping!(api, api::FormattingElement => FmtElement {
|
||||||
Indent(v => v.iter().map(FmtElement::from_api).collect()),
|
Indent(v => v.iter().map(FmtElement::from_api).collect()),
|
||||||
@@ -95,6 +125,7 @@ impl FmtElement {
|
|||||||
Sub{ *slot, *bounded },
|
Sub{ *slot, *bounded },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/// Encode to message
|
||||||
pub fn to_api(&self) -> api::FormattingElement {
|
pub fn to_api(&self) -> api::FormattingElement {
|
||||||
match_mapping!(self, FmtElement => api::FormattingElement {
|
match_mapping!(self, FmtElement => api::FormattingElement {
|
||||||
Indent(v => v.iter().map(FmtElement::to_api).collect()),
|
Indent(v => v.iter().map(FmtElement::to_api).collect()),
|
||||||
@@ -104,39 +135,16 @@ impl FmtElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A particular way in which a value may be formatted in text.
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct Variant {
|
pub struct FmtVariant {
|
||||||
|
/// Whether this representation has an intrinsic end marker or it needs the
|
||||||
|
/// parent to provide one
|
||||||
pub bounded: bool,
|
pub bounded: bool,
|
||||||
|
/// Template string syntax elements
|
||||||
pub elements: Vec<FmtElement>,
|
pub elements: Vec<FmtElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn variants_parse_test() {
|
|
||||||
let vars = Rc::new(Variants::default().bounded("({{{0}}})"));
|
|
||||||
let expected_vars = Rc::new(Variants(vec![Variant {
|
|
||||||
bounded: true,
|
|
||||||
elements: vec![
|
|
||||||
FmtElement::String(Rc::new("({".to_string())),
|
|
||||||
FmtElement::Sub { bounded: Some(false), slot: 0 },
|
|
||||||
FmtElement::String(Rc::new("})".to_string())),
|
|
||||||
],
|
|
||||||
}]));
|
|
||||||
assert_eq!(vars.as_ref(), expected_vars.as_ref());
|
|
||||||
let unit = vars.units(["1".into()]);
|
|
||||||
assert_eq!(unit, FmtUnit {
|
|
||||||
subs: vec![FmtUnit {
|
|
||||||
subs: vec![],
|
|
||||||
variants: Rc::new(Variants(vec![Variant {
|
|
||||||
bounded: true,
|
|
||||||
elements: vec![FmtElement::String(Rc::new("1".to_string()))]
|
|
||||||
}]))
|
|
||||||
}],
|
|
||||||
variants: expected_vars
|
|
||||||
});
|
|
||||||
let str = take_first(&unit, true);
|
|
||||||
assert_eq!(str, "({1})");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a collection of formatting strings for the same set of parameters
|
/// Represents a collection of formatting strings for the same set of parameters
|
||||||
/// from which the formatter can choose within their associated constraints.
|
/// from which the formatter can choose within their associated constraints.
|
||||||
///
|
///
|
||||||
@@ -145,7 +153,7 @@ fn variants_parse_test() {
|
|||||||
/// - {0l} causes the current end restriction to be applied to the parameter.
|
/// - {0l} causes the current end restriction to be applied to the parameter.
|
||||||
/// This is to be used if the parameter is at the very end of the variant.
|
/// This is to be used if the parameter is at the very end of the variant.
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq, Default)]
|
||||||
pub struct Variants(pub Vec<Variant>);
|
pub struct Variants(pub Vec<FmtVariant>);
|
||||||
impl Variants {
|
impl Variants {
|
||||||
fn parse_phs(s: &'_ str) -> Vec<FmtElement> {
|
fn parse_phs(s: &'_ str) -> Vec<FmtElement> {
|
||||||
let re = Regex::new(r"(?<tpl>\{\d+?[bl]?\})|(\{\{)|(\}\})").unwrap();
|
let re = Regex::new(r"(?<tpl>\{\d+?[bl]?\})|(\{\{)|(\}\})").unwrap();
|
||||||
@@ -216,7 +224,7 @@ impl Variants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn add(&mut self, bounded: bool, s: &'_ str) {
|
fn add(&mut self, bounded: bool, s: &'_ str) {
|
||||||
self.0.push(Variant { bounded, elements: Self::parse(s) })
|
self.0.push(FmtVariant { bounded, elements: Self::parse(s) })
|
||||||
}
|
}
|
||||||
/// This option is available in all positions.
|
/// This option is available in all positions.
|
||||||
/// See [Variants] for a description of the format strings
|
/// See [Variants] for a description of the format strings
|
||||||
@@ -231,35 +239,42 @@ impl Variants {
|
|||||||
self.add(false, s);
|
self.add(false, s);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Produces formatting options for `len` parameters separated by `delim`.
|
||||||
|
/// `seq_bnd` indicates whether `delim` and `tail` can unambiguously indicate
|
||||||
|
/// the end of a subsequence. For consistency, the stricter of the two is
|
||||||
|
/// expected to be used
|
||||||
pub fn sequence(
|
pub fn sequence(
|
||||||
mut self,
|
mut self,
|
||||||
len: usize,
|
len: usize,
|
||||||
head: &str,
|
head: &str,
|
||||||
delim: &str,
|
delim: &str,
|
||||||
tail: &str,
|
tail: &str,
|
||||||
seq_bnd: Option<bool>,
|
seq_bnd: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let seq = chain!(
|
let seq = chain!(
|
||||||
[FmtElement::str(head)],
|
[FmtElement::str(head)],
|
||||||
Itertools::intersperse(
|
Itertools::intersperse(
|
||||||
FmtElement::sequence(len, seq_bnd).into_iter(),
|
FmtElement::sequence(len, Some(seq_bnd)).into_iter(),
|
||||||
FmtElement::str(delim),
|
FmtElement::str(delim),
|
||||||
),
|
),
|
||||||
[FmtElement::str(tail)],
|
[FmtElement::str(tail)],
|
||||||
);
|
);
|
||||||
self.0.push(Variant { bounded: true, elements: seq.collect_vec() });
|
self.0.push(FmtVariant { bounded: true, elements: seq.collect_vec() });
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
/// Pair the slots with subunits to produce a [FmtUnit]
|
||||||
pub fn units_own(self, subs: impl IntoIterator<Item = FmtUnit>) -> FmtUnit {
|
pub fn units_own(self, subs: impl IntoIterator<Item = FmtUnit>) -> FmtUnit {
|
||||||
FmtUnit::new(Rc::new(self), subs)
|
FmtUnit::new(Rc::new(self), subs)
|
||||||
}
|
}
|
||||||
|
/// Pair the slots with subunits to produce a [FmtUnit] by reference. These
|
||||||
|
/// objects should preferably be thread-locally cached whenever possible.
|
||||||
pub fn units(self: &Rc<Self>, subs: impl IntoIterator<Item = FmtUnit>) -> FmtUnit {
|
pub fn units(self: &Rc<Self>, subs: impl IntoIterator<Item = FmtUnit>) -> FmtUnit {
|
||||||
FmtUnit::new(self.clone(), subs)
|
FmtUnit::new(self.clone(), subs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<Rc<String>> for Variants {
|
impl From<Rc<String>> for Variants {
|
||||||
fn from(value: Rc<String>) -> Self {
|
fn from(value: Rc<String>) -> Self {
|
||||||
Self(vec![Variant { elements: vec![FmtElement::String(value)], bounded: true }])
|
Self(vec![FmtVariant { elements: vec![FmtElement::String(value)], bounded: true }])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<String> for Variants {
|
impl From<String> for Variants {
|
||||||
@@ -300,29 +315,22 @@ pub fn take_first(unit: &FmtUnit, bounded: bool) -> String {
|
|||||||
fill_slots(&first.elements, &unit.subs, 0, bounded)
|
fill_slots(&first.elements, &unit.subs, 0, bounded)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn take_first_fmt(v: &(impl Format + ?Sized), i: &Interner) -> String {
|
pub async fn take_first_fmt(v: &(impl Format + ?Sized)) -> String {
|
||||||
take_first(&v.print(&FmtCtxImpl { i }).await, false)
|
take_first(&v.print(&FmtCtxImpl { _foo: PhantomData }).await, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Default] this if you need one
|
||||||
|
#[derive(Default)]
|
||||||
pub struct FmtCtxImpl<'a> {
|
pub struct FmtCtxImpl<'a> {
|
||||||
pub i: &'a Interner,
|
_foo: PhantomData<&'a ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FmtCtx {
|
/// Additional settings to the formatter. Implemented by [FmtCtxImpl]. Currently
|
||||||
fn i(&self) -> &Interner;
|
/// not in use
|
||||||
// fn print_as(&self, p: &(impl Format + ?Sized)) -> impl Future<Output =
|
pub trait FmtCtx {}
|
||||||
// String> where Self: Sized {
|
impl FmtCtx for FmtCtxImpl<'_> {}
|
||||||
// async {
|
|
||||||
// // for now, always take the first option which is probably the one-line
|
|
||||||
// form let variants = p.print(self).await;
|
|
||||||
// take_first(&variants, true)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
impl FmtCtx for FmtCtxImpl<'_> {
|
|
||||||
fn i(&self) -> &Interner { self.i }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// A value that can be formatted into a string with multiple possible forms
|
||||||
pub trait Format {
|
pub trait Format {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> impl Future<Output = FmtUnit> + 'a;
|
fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> impl Future<Output = FmtUnit> + 'a;
|
||||||
@@ -332,13 +340,44 @@ impl Format for Never {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Format with default strategy. Currently equal to [take_first_fmt]
|
/// Format with default strategy. Currently equal to [take_first_fmt]
|
||||||
pub async fn fmt(v: &(impl Format + ?Sized), i: &Interner) -> String { take_first_fmt(v, i).await }
|
pub async fn fmt(v: &(impl Format + ?Sized)) -> String { take_first_fmt(v).await }
|
||||||
/// Format a sequence with default strategy. Currently equal to [take_first_fmt]
|
/// Format a sequence with default strategy. Currently equal to [take_first_fmt]
|
||||||
pub async fn fmt_v<F: Format + ?Sized, R: Borrow<F>>(
|
pub async fn fmt_v<F: Format + ?Sized>(
|
||||||
v: impl IntoIterator<Item = R>,
|
v: impl IntoIterator<Item: Borrow<F>>,
|
||||||
i: &Interner,
|
|
||||||
) -> impl Iterator<Item = String> {
|
) -> impl Iterator<Item = String> {
|
||||||
join_all(v.into_iter().map(|f| async move { take_first_fmt(f.borrow(), i).await }))
|
join_all(v.into_iter().map(|f| async move { take_first_fmt(f.borrow()).await })).await.into_iter()
|
||||||
.await
|
}
|
||||||
.into_iter()
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::format::{FmtElement, FmtUnit, FmtVariant, Variants, take_first};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn variants_parse_test() {
|
||||||
|
let vars = Rc::new(Variants::default().bounded("({{{0}}})"));
|
||||||
|
let expected_vars = Rc::new(Variants(vec![FmtVariant {
|
||||||
|
bounded: true,
|
||||||
|
elements: vec![
|
||||||
|
FmtElement::String(Rc::new("({".to_string())),
|
||||||
|
FmtElement::Sub { bounded: Some(false), slot: 0 },
|
||||||
|
FmtElement::String(Rc::new("})".to_string())),
|
||||||
|
],
|
||||||
|
}]));
|
||||||
|
assert_eq!(vars.as_ref(), expected_vars.as_ref());
|
||||||
|
let unit = vars.units(["1".into()]);
|
||||||
|
assert_eq!(unit, FmtUnit {
|
||||||
|
subs: vec![FmtUnit {
|
||||||
|
subs: vec![],
|
||||||
|
variants: Rc::new(Variants(vec![FmtVariant {
|
||||||
|
bounded: true,
|
||||||
|
elements: vec![FmtElement::String(Rc::new("1".to_string()))]
|
||||||
|
}]))
|
||||||
|
}],
|
||||||
|
variants: expected_vars
|
||||||
|
});
|
||||||
|
let str = take_first(&unit, true);
|
||||||
|
assert_eq!(str, "({1})");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
//! Impure functions that can be triggered by Orchid code when a command
|
|
||||||
//! evaluates to an atom representing a command
|
|
||||||
|
|
||||||
use std::any::{Any, TypeId};
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use hashbrown::HashMap;
|
|
||||||
use trait_set::trait_set;
|
|
||||||
|
|
||||||
use super::nort::Expr;
|
|
||||||
use crate::foreign::atom::Atomic;
|
|
||||||
use crate::foreign::error::RTResult;
|
|
||||||
use crate::foreign::to_clause::ToClause;
|
|
||||||
use crate::location::CodeLocation;
|
|
||||||
|
|
||||||
trait_set! {
|
|
||||||
trait Handler = for<'a> Fn(&'a dyn Any, CodeLocation) -> Expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum HTEntry<'a> {
|
|
||||||
Handler(Box<dyn Handler + 'a>),
|
|
||||||
Forward(&'a (dyn Handler + 'a)),
|
|
||||||
}
|
|
||||||
impl<'a> AsRef<dyn Handler + 'a> for HTEntry<'a> {
|
|
||||||
fn as_ref(&self) -> &(dyn Handler + 'a) {
|
|
||||||
match self {
|
|
||||||
HTEntry::Handler(h) => &**h,
|
|
||||||
HTEntry::Forward(h) => *h,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A table of impure command handlers exposed to Orchid
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct HandlerTable<'a> {
|
|
||||||
handlers: HashMap<TypeId, HTEntry<'a>>,
|
|
||||||
}
|
|
||||||
impl<'a> HandlerTable<'a> {
|
|
||||||
/// Create a new [HandlerTable]
|
|
||||||
#[must_use]
|
|
||||||
pub fn new() -> Self { Self { handlers: HashMap::new() } }
|
|
||||||
|
|
||||||
/// Add a handler function to interpret a command and select the continuation.
|
|
||||||
/// See [HandlerTable#with] for a declarative option.
|
|
||||||
pub fn register<T: 'static, R: ToClause>(&mut self, f: impl for<'b> FnMut(&'b T) -> R + 'a) {
|
|
||||||
let cell = RefCell::new(f);
|
|
||||||
let cb = move |a: &dyn Any, loc: CodeLocation| {
|
|
||||||
cell.borrow_mut()(a.downcast_ref().expect("found by TypeId")).to_expr(loc)
|
|
||||||
};
|
|
||||||
let prev = self.handlers.insert(TypeId::of::<T>(), HTEntry::Handler(Box::new(cb)));
|
|
||||||
assert!(prev.is_none(), "A handler for this type is already registered");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a handler function to interpret a command and select the continuation.
|
|
||||||
/// See [HandlerTable#register] for a procedural option.
|
|
||||||
pub fn with<T: 'static>(mut self, f: impl FnMut(&T) -> RTResult<Expr> + 'a) -> Self {
|
|
||||||
self.register(f);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find and execute the corresponding handler for this type
|
|
||||||
pub fn dispatch(&self, arg: &dyn Atomic, loc: CodeLocation) -> Option<Expr> {
|
|
||||||
(self.handlers.get(&arg.as_any_ref().type_id())).map(|ent| ent.as_ref()(arg.as_any_ref(), loc))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Combine two non-overlapping handler sets
|
|
||||||
#[must_use]
|
|
||||||
pub fn combine(mut self, other: Self) -> Self {
|
|
||||||
for (key, value) in other.handlers {
|
|
||||||
let prev = self.handlers.insert(key, value);
|
|
||||||
assert!(prev.is_none(), "Duplicate handlers")
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add entries that forward requests to a borrowed non-overlapping handler
|
|
||||||
/// set
|
|
||||||
pub fn link<'b: 'a>(mut self, other: &'b HandlerTable<'b>) -> Self {
|
|
||||||
for (key, value) in other.handlers.iter() {
|
|
||||||
let prev = self.handlers.insert(*key, HTEntry::Forward(value.as_ref()));
|
|
||||||
assert!(prev.is_none(), "Duplicate handlers")
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[allow(unconditional_recursion)]
|
|
||||||
#[allow(clippy::ptr_arg)]
|
|
||||||
mod test {
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
use super::HandlerTable;
|
|
||||||
|
|
||||||
/// Ensure that the method I use to verify covariance actually passes with
|
|
||||||
/// covariant and fails with invariant
|
|
||||||
///
|
|
||||||
/// The failing case:
|
|
||||||
/// ```
|
|
||||||
/// struct Cov2<'a>(PhantomData<&'a mut &'a ()>);
|
|
||||||
/// fn fail<'a>(_c: &Cov2<'a>, _s: &'a String) { fail(_c, &String::new()) }
|
|
||||||
/// ```
|
|
||||||
#[allow(unused)]
|
|
||||||
fn covariant_control() {
|
|
||||||
struct Cov<'a>(PhantomData<&'a ()>);
|
|
||||||
fn pass<'a>(_c: &Cov<'a>, _s: &'a String) { pass(_c, &String::new()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The &mut ensures that 'a in the two functions must be disjoint, and that
|
|
||||||
/// ht must outlive both. For this to compile, Rust has to cast ht to the
|
|
||||||
/// shorter lifetimes, ensuring covariance
|
|
||||||
#[allow(unused)]
|
|
||||||
fn assert_covariant() {
|
|
||||||
fn pass<'a>(_ht: HandlerTable<'a>, _s: &'a String) { pass(_ht, &String::new()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +1,120 @@
|
|||||||
use std::num::NonZeroU64;
|
use std::ops::{Index, IndexMut};
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
|
||||||
use std::sync::{Mutex, MutexGuard, OnceLock};
|
|
||||||
|
|
||||||
use hashbrown::HashMap;
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
enum Rec<T> {
|
||||||
|
Val(T),
|
||||||
|
Next(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple and very fast store that assigns small stable integer IDs to
|
||||||
|
/// objects. It uses a free-list for O(1) insertion, deletion and retrieval.
|
||||||
pub struct IdStore<T> {
|
pub struct IdStore<T> {
|
||||||
table: OnceLock<Mutex<HashMap<NonZeroU64, T>>>,
|
first: usize,
|
||||||
id: AtomicU64,
|
values: Vec<Rec<T>>,
|
||||||
}
|
}
|
||||||
impl<T> IdStore<T> {
|
impl<T> IdStore<T> {
|
||||||
pub const fn new() -> Self { Self { table: OnceLock::new(), id: AtomicU64::new(1) } }
|
pub fn new() -> Self { IdStore { first: 0, values: Vec::new() } }
|
||||||
pub fn add(&self, t: T) -> IdRecord<'_, T> {
|
pub fn add(&mut self, value: T) -> usize {
|
||||||
let tbl = self.table.get_or_init(Mutex::default);
|
if self.first == 0 && self.values.is_empty() {
|
||||||
let mut tbl_g = tbl.lock().unwrap();
|
self.first = 1;
|
||||||
let id: NonZeroU64 = self.id.fetch_add(1, Ordering::Relaxed).try_into().unwrap();
|
self.values.push(Rec::Val(value));
|
||||||
assert!(tbl_g.insert(id, t).is_none(), "atom ID wraparound");
|
return 0;
|
||||||
IdRecord(id, tbl_g)
|
}
|
||||||
|
if self.first == self.values.len() {
|
||||||
|
let len = self.values.len();
|
||||||
|
self.values.extend((len..len * 2).map(|i| Rec::Next(i + 1)));
|
||||||
|
}
|
||||||
|
let Some(rec) = self.values.get_mut(self.first) else {
|
||||||
|
panic!("Bounds check and growth above")
|
||||||
|
};
|
||||||
|
let Rec::Next(next) = rec else {
|
||||||
|
panic!("first should always point to an empty space or one past the length")
|
||||||
|
};
|
||||||
|
let id = std::mem::replace(&mut self.first, *next);
|
||||||
|
*rec = Rec::Val(value);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
pub fn add_with(&mut self, cb: impl FnOnce(usize) -> T) -> usize { self.add(cb(self.first)) }
|
||||||
|
pub fn remove(&mut self, id: usize) -> T {
|
||||||
|
let Some(rec) = self.values.get_mut(id) else { panic!("Index out of bounds") };
|
||||||
|
let Rec::Val(val) = std::mem::replace(rec, Rec::Next(self.first)) else {
|
||||||
|
panic!("Index vacated")
|
||||||
|
};
|
||||||
|
self.first = id;
|
||||||
|
val
|
||||||
|
}
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (usize, &T)> {
|
||||||
|
(self.values.iter().enumerate())
|
||||||
|
.filter_map(|(i, rec)| if let Rec::Val(val) = rec { Some((i, val)) } else { None })
|
||||||
|
}
|
||||||
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = (usize, &mut T)> {
|
||||||
|
(self.values.iter_mut().enumerate())
|
||||||
|
.filter_map(|(i, rec)| if let Rec::Val(val) = rec { Some((i, val)) } else { None })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(clippy::type_complexity, reason = "This is verbose enough as it is")]
|
||||||
|
pub struct IntoIter<T>(
|
||||||
|
std::iter::FilterMap<
|
||||||
|
std::iter::Enumerate<std::vec::IntoIter<Rec<T>>>,
|
||||||
|
fn((usize, Rec<T>)) -> Option<(usize, T)>,
|
||||||
|
>,
|
||||||
|
);
|
||||||
|
impl<T> Iterator for IntoIter<T> {
|
||||||
|
type Item = (usize, T);
|
||||||
|
fn next(&mut self) -> Option<Self::Item> { self.0.next() }
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) { self.0.size_hint() }
|
||||||
|
}
|
||||||
|
impl<T> IntoIterator for IdStore<T> {
|
||||||
|
type Item = (usize, T);
|
||||||
|
type IntoIter = IntoIter<T>;
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
IntoIter(
|
||||||
|
(self.values.into_iter().enumerate())
|
||||||
|
.filter_map(|(i, rec)| if let Rec::Val(val) = rec { Some((i, val)) } else { None }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> Index<usize> for IdStore<T> {
|
||||||
|
type Output = T;
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
match self.values.get(index) {
|
||||||
|
Some(Rec::Val(val)) => val,
|
||||||
|
_ => panic!("Invalid or stale index"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> IndexMut<usize> for IdStore<T> {
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
match self.values.get_mut(index) {
|
||||||
|
Some(Rec::Val(val)) => val,
|
||||||
|
_ => panic!("Invalid or stale index"),
|
||||||
}
|
}
|
||||||
pub fn get(&self, id: impl Into<NonZeroU64>) -> Option<IdRecord<'_, T>> {
|
|
||||||
let tbl = self.table.get_or_init(Mutex::default);
|
|
||||||
let tbl_g = tbl.lock().unwrap();
|
|
||||||
let id64 = id.into();
|
|
||||||
if tbl_g.contains_key(&id64) { Some(IdRecord(id64, tbl_g)) } else { None }
|
|
||||||
}
|
}
|
||||||
pub fn is_empty(&self) -> bool { self.len() == 0 }
|
|
||||||
pub fn len(&self) -> usize { self.table.get().map(|t| t.lock().unwrap().len()).unwrap_or(0) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for IdStore<T> {
|
impl<T> Default for IdStore<T> {
|
||||||
fn default() -> Self { Self::new() }
|
fn default() -> Self { Self::new() }
|
||||||
}
|
}
|
||||||
|
impl<A> FromIterator<A> for IdStore<A> {
|
||||||
|
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
|
||||||
|
let values = iter.into_iter().map(|a| Rec::Val(a)).collect_vec();
|
||||||
|
Self { first: values.len(), values }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct IdRecord<'a, T>(NonZeroU64, MutexGuard<'a, HashMap<NonZeroU64, T>>);
|
#[cfg(test)]
|
||||||
impl<T> IdRecord<'_, T> {
|
mod test {
|
||||||
pub fn id(&self) -> NonZeroU64 { self.0 }
|
use super::*;
|
||||||
pub fn remove(mut self) -> T { self.1.remove(&self.0).unwrap() }
|
|
||||||
}
|
#[test]
|
||||||
impl<T> Deref for IdRecord<'_, T> {
|
fn add_and_retrieve() {
|
||||||
type Target = T;
|
let mut store = IdStore::new();
|
||||||
fn deref(&self) -> &Self::Target {
|
let key1 = store.add(14);
|
||||||
self.1.get(&self.0).expect("Existence checked on construction")
|
let key2 = store.add(34);
|
||||||
}
|
assert_eq!(store[key1], 14);
|
||||||
}
|
assert_eq!(store[key2], 34);
|
||||||
impl<T> DerefMut for IdRecord<'_, T> {
|
assert_eq!(store.remove(key1), 14);
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
assert_eq!(store.iter().collect_vec(), vec![(key2, &34)]);
|
||||||
self.1.get_mut(&self.0).expect("Existence checked on construction")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,310 +1,424 @@
|
|||||||
use std::borrow::Borrow;
|
use std::fmt::{Debug, Display};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::hash::BuildHasher as _;
|
use std::hash::Hash;
|
||||||
use std::num::NonZeroU64;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::atomic;
|
|
||||||
use std::{fmt, hash};
|
use std::{fmt, hash};
|
||||||
|
|
||||||
use futures::lock::Mutex;
|
use futures::future::LocalBoxFuture;
|
||||||
use hashbrown::{HashMap, HashSet};
|
use task_local::task_local;
|
||||||
use itertools::Itertools as _;
|
|
||||||
use orchid_api_traits::Request;
|
|
||||||
|
|
||||||
use crate::api;
|
use crate::api;
|
||||||
use crate::reqnot::{DynRequester, Requester};
|
|
||||||
|
|
||||||
/// Clippy crashes while verifying `Tok: Sized` without this and I cba to create
|
/// Implementation-specific backing object for an interned string.
|
||||||
/// a minimal example
|
pub trait IStrHandle: AsRef<str> {
|
||||||
|
fn rc(&self) -> Rc<String>;
|
||||||
|
}
|
||||||
|
/// Implementation-specific backing object for an interned sequence of interned
|
||||||
|
/// strings.
|
||||||
|
pub trait IStrvHandle: AsRef<[IStr]> {
|
||||||
|
fn rc(&self) -> Rc<Vec<IStr>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interned string created with [is] or [es]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct ForceSized<T>(T);
|
pub struct IStr(pub api::TStr, pub Rc<dyn IStrHandle>);
|
||||||
|
impl IStr {
|
||||||
|
/// Obtain a unique ID for this interned data
|
||||||
|
///
|
||||||
|
/// NOTICE: the ID is guaranteed to be the same for any interned instance of
|
||||||
|
/// the same value only as long as at least one instance exists. If a value is
|
||||||
|
/// no longer interned, the interner is free to forget about it
|
||||||
|
pub fn to_api(&self) -> api::TStr { self.0 }
|
||||||
|
/// Owned reference to a shared instance of the interned string
|
||||||
|
pub fn rc(&self) -> Rc<String> { self.1.rc() }
|
||||||
|
}
|
||||||
|
impl Deref for IStr {
|
||||||
|
type Target = str;
|
||||||
|
fn deref(&self) -> &Self::Target { self.1.as_ref().as_ref() }
|
||||||
|
}
|
||||||
|
impl Eq for IStr {}
|
||||||
|
impl PartialEq for IStr {
|
||||||
|
fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
|
||||||
|
}
|
||||||
|
impl Hash for IStr {
|
||||||
|
fn hash<H: hash::Hasher>(&self, state: &mut H) { self.0.hash(state) }
|
||||||
|
}
|
||||||
|
impl Display for IStr {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.deref()) }
|
||||||
|
}
|
||||||
|
impl Debug for IStr {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "IStr({self}") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Interned string sequence
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Tok<T: Interned> {
|
pub struct IStrv(pub api::TStrv, pub Rc<dyn IStrvHandle>);
|
||||||
data: Rc<T>,
|
impl IStrv {
|
||||||
marker: ForceSized<T::Marker>,
|
/// Obtain a unique ID for this interned data
|
||||||
|
///
|
||||||
|
/// NOTICE: the ID is guaranteed to be the same for any interned instance of
|
||||||
|
/// the same value only as long as at least one instance exists. If a value is
|
||||||
|
/// no longer interned, the interner is free to forget about it
|
||||||
|
pub fn to_api(&self) -> api::TStrv { self.0 }
|
||||||
|
/// Owned reference to a shared instance of the interned sequence
|
||||||
|
pub fn rc(&self) -> Rc<Vec<IStr>> { self.1.rc() }
|
||||||
}
|
}
|
||||||
impl<T: Interned> Tok<T> {
|
impl Deref for IStrv {
|
||||||
pub fn new(data: Rc<T>, marker: T::Marker) -> Self { Self { data, marker: ForceSized(marker) } }
|
type Target = [IStr];
|
||||||
pub fn to_api(&self) -> T::Marker { self.marker.0 }
|
fn deref(&self) -> &Self::Target { self.1.as_ref().as_ref() }
|
||||||
pub async fn from_api<M>(marker: M, i: &Interner) -> Self
|
|
||||||
where M: InternMarker<Interned = T> {
|
|
||||||
i.ex(marker).await
|
|
||||||
}
|
|
||||||
pub fn rc(&self) -> Rc<T> { self.data.clone() }
|
|
||||||
}
|
}
|
||||||
impl<T: Interned> Deref for Tok<T> {
|
impl Eq for IStrv {}
|
||||||
type Target = T;
|
impl PartialEq for IStrv {
|
||||||
|
fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
|
||||||
fn deref(&self) -> &Self::Target { self.data.as_ref() }
|
|
||||||
}
|
}
|
||||||
impl<T: Interned> Ord for Tok<T> {
|
impl Hash for IStrv {
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.to_api().cmp(&other.to_api()) }
|
fn hash<H: hash::Hasher>(&self, state: &mut H) { self.0.0.hash(state) }
|
||||||
}
|
}
|
||||||
impl<T: Interned> PartialOrd for Tok<T> {
|
impl Display for IStrv {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
|
|
||||||
}
|
|
||||||
impl<T: Interned> Eq for Tok<T> {}
|
|
||||||
impl<T: Interned> PartialEq for Tok<T> {
|
|
||||||
fn eq(&self, other: &Self) -> bool { self.cmp(other).is_eq() }
|
|
||||||
}
|
|
||||||
impl<T: Interned> hash::Hash for Tok<T> {
|
|
||||||
fn hash<H: hash::Hasher>(&self, state: &mut H) { self.to_api().hash(state) }
|
|
||||||
}
|
|
||||||
impl<T: Interned + fmt::Display> fmt::Display for Tok<T> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", &*self.data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: Interned + fmt::Debug> fmt::Debug for Tok<T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "Token({} -> {:?})", self.to_api().get_id(), self.data.as_ref())
|
let mut iter = self.deref().iter();
|
||||||
|
match iter.next() {
|
||||||
|
None => return Ok(()),
|
||||||
|
Some(s) => write!(f, "{s}")?,
|
||||||
|
}
|
||||||
|
for s in iter {
|
||||||
|
write!(f, "::{s}")?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Debug for IStrv {
|
||||||
pub trait Interned: Eq + hash::Hash + Clone + fmt::Debug + Internable<Interned = Self> {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "IStrv({self})") }
|
||||||
type Marker: InternMarker<Interned = Self> + Sized;
|
|
||||||
fn intern(
|
|
||||||
self: Rc<Self>,
|
|
||||||
req: &(impl DynRequester<Transfer = api::IntReq> + ?Sized),
|
|
||||||
) -> impl Future<Output = Self::Marker>;
|
|
||||||
fn bimap(interner: &mut TypedInterners) -> &mut Bimap<Self>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Internable: fmt::Debug {
|
/// Injectable interner interface
|
||||||
type Interned: Interned;
|
///
|
||||||
fn get_owned(&self) -> Rc<Self::Interned>;
|
/// [Self::is] and [Self::iv] return an existing ID if any [IStrHandle] or
|
||||||
|
/// [IStrvHandle] for the same value is still live, and any ID currently not
|
||||||
|
/// used with the same type otherwise
|
||||||
|
///
|
||||||
|
/// [Self::es] and [Self::ev] find an existing value by its key if any
|
||||||
|
/// [IStrHandle] or [IStrvHandle] for the same ID is still live. If all objects
|
||||||
|
/// are gone the functions may work or panic.
|
||||||
|
pub trait InternerSrv {
|
||||||
|
/// Intern a string
|
||||||
|
fn is<'a>(&'a self, v: &'a str) -> LocalBoxFuture<'a, IStr>;
|
||||||
|
/// Find an existing string by its key
|
||||||
|
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, IStr>;
|
||||||
|
/// Intern a str vector
|
||||||
|
fn iv<'a>(&'a self, v: &'a [IStr]) -> LocalBoxFuture<'a, IStrv>;
|
||||||
|
/// Find an existing str vector by its key
|
||||||
|
fn ev(&self, t: api::TStrv) -> LocalBoxFuture<'_, IStrv>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InternMarker: Copy + PartialEq + Eq + PartialOrd + Ord + hash::Hash + Sized {
|
task_local! {
|
||||||
type Interned: Interned<Marker = Self>;
|
static INTERNER: Rc<dyn InternerSrv>;
|
||||||
/// Only called on replicas
|
|
||||||
fn resolve(self, i: &Interner) -> impl Future<Output = Tok<Self::Interned>>;
|
|
||||||
fn get_id(self) -> NonZeroU64;
|
|
||||||
fn from_id(id: NonZeroU64) -> Self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interned for String {
|
/// Install a global interner. Within this future, the global [is], [iv], [es]
|
||||||
type Marker = api::TStr;
|
/// and [ev] functions call the provided [InternerSrv]
|
||||||
async fn intern(
|
pub async fn with_interner<F: Future>(val: Rc<dyn InternerSrv>, fut: F) -> F::Output {
|
||||||
self: Rc<Self>,
|
INTERNER.scope(val, fut).await
|
||||||
req: &(impl DynRequester<Transfer = api::IntReq> + ?Sized),
|
|
||||||
) -> Self::Marker {
|
|
||||||
req.request(api::InternStr(self.to_string())).await
|
|
||||||
}
|
|
||||||
fn bimap(interners: &mut TypedInterners) -> &mut Bimap<Self> { &mut interners.strings }
|
|
||||||
}
|
|
||||||
impl InternMarker for api::TStr {
|
|
||||||
type Interned = String;
|
|
||||||
async fn resolve(self, i: &Interner) -> Tok<Self::Interned> {
|
|
||||||
Tok::new(Rc::new(i.0.master.as_ref().unwrap().request(api::ExternStr(self)).await), self)
|
|
||||||
}
|
|
||||||
fn get_id(self) -> NonZeroU64 { self.0 }
|
|
||||||
fn from_id(id: NonZeroU64) -> Self { Self(id) }
|
|
||||||
}
|
|
||||||
impl Internable for str {
|
|
||||||
type Interned = String;
|
|
||||||
fn get_owned(&self) -> Rc<Self::Interned> { Rc::new(self.to_string()) }
|
|
||||||
}
|
|
||||||
impl Internable for String {
|
|
||||||
type Interned = String;
|
|
||||||
fn get_owned(&self) -> Rc<Self::Interned> { Rc::new(self.to_string()) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interned for Vec<Tok<String>> {
|
fn get_interner() -> Rc<dyn InternerSrv> {
|
||||||
type Marker = api::TStrv;
|
INTERNER.try_with(|i| i.clone()).expect("Interner not initialized")
|
||||||
async fn intern(
|
|
||||||
self: Rc<Self>,
|
|
||||||
req: &(impl DynRequester<Transfer = api::IntReq> + ?Sized),
|
|
||||||
) -> Self::Marker {
|
|
||||||
req.request(api::InternStrv(self.iter().map(|t| t.to_api()).collect())).await
|
|
||||||
}
|
|
||||||
fn bimap(interners: &mut TypedInterners) -> &mut Bimap<Self> { &mut interners.vecs }
|
|
||||||
}
|
|
||||||
impl InternMarker for api::TStrv {
|
|
||||||
type Interned = Vec<Tok<String>>;
|
|
||||||
async fn resolve(self, i: &Interner) -> Tok<Self::Interned> {
|
|
||||||
let rep = i.0.master.as_ref().unwrap().request(api::ExternStrv(self)).await;
|
|
||||||
let data = futures::future::join_all(rep.into_iter().map(|m| i.ex(m))).await;
|
|
||||||
Tok::new(Rc::new(data), self)
|
|
||||||
}
|
|
||||||
fn get_id(self) -> NonZeroU64 { self.0 }
|
|
||||||
fn from_id(id: NonZeroU64) -> Self { Self(id) }
|
|
||||||
}
|
|
||||||
impl Internable for [Tok<String>] {
|
|
||||||
type Interned = Vec<Tok<String>>;
|
|
||||||
fn get_owned(&self) -> Rc<Self::Interned> { Rc::new(self.to_vec()) }
|
|
||||||
}
|
|
||||||
impl<const N: usize> Internable for [Tok<String>; N] {
|
|
||||||
type Interned = Vec<Tok<String>>;
|
|
||||||
fn get_owned(&self) -> Rc<Self::Interned> { Rc::new(self.to_vec()) }
|
|
||||||
}
|
|
||||||
impl Internable for Vec<Tok<String>> {
|
|
||||||
type Interned = Vec<Tok<String>>;
|
|
||||||
fn get_owned(&self) -> Rc<Self::Interned> { Rc::new(self.to_vec()) }
|
|
||||||
}
|
|
||||||
// impl Internable for Vec<api::TStr> {
|
|
||||||
// type Interned = Vec<Tok<String>>;
|
|
||||||
// fn get_owned(&self) -> Arc<Self::Interned> {
|
|
||||||
// Arc::new(self.iter().map(|ts| deintern(*ts)).collect())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl Internable for [api::TStr] {
|
|
||||||
// type Interned = Vec<Tok<String>>;
|
|
||||||
// fn get_owned(&self) -> Arc<Self::Interned> {
|
|
||||||
// Arc::new(self.iter().map(|ts| deintern(*ts)).collect())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// The number of references held to any token by the interner.
|
|
||||||
const BASE_RC: usize = 3;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn base_rc_correct() {
|
|
||||||
let tok = Tok::new(Rc::new("foo".to_string()), api::TStr(1.try_into().unwrap()));
|
|
||||||
let mut bimap = Bimap::default();
|
|
||||||
bimap.insert(tok.clone());
|
|
||||||
assert_eq!(Rc::strong_count(&tok.data), BASE_RC + 1, "the bimap plus the current instance");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Bimap<T: Interned> {
|
/// Intern a `String` (find its ID or assign it a new one)
|
||||||
intern: HashMap<Rc<T>, Tok<T>>,
|
pub async fn is(v: &str) -> IStr { get_interner().is(v).await }
|
||||||
by_id: HashMap<T::Marker, Tok<T>>,
|
/// Intern a `Vec<IStr>` (find its ID or assign it a new one)
|
||||||
}
|
pub async fn iv(v: &[IStr]) -> IStrv { get_interner().iv(v).await }
|
||||||
impl<T: Interned> Bimap<T> {
|
/// Find a live [IStr] by its ID
|
||||||
pub fn insert(&mut self, token: Tok<T>) {
|
///
|
||||||
self.intern.insert(token.data.clone(), token.clone());
|
/// # Panics
|
||||||
self.by_id.insert(token.to_api(), token);
|
///
|
||||||
}
|
/// This function may panic if there are no other references to the [IStr] we're
|
||||||
|
/// searching for, as the interner is free to forget about unreferenced values
|
||||||
|
pub async fn es(v: api::TStr) -> IStr { get_interner().es(v).await }
|
||||||
|
/// Find a live [IStrv] by its ID
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function may panic if there are no other references to the [IStrv]
|
||||||
|
/// we're searching for, as the interner is free to forget about unreferenced
|
||||||
|
/// values
|
||||||
|
pub async fn ev(v: api::TStrv) -> IStrv { get_interner().ev(v).await }
|
||||||
|
|
||||||
pub fn by_marker(&self, marker: T::Marker) -> Option<Tok<T>> { self.by_id.get(&marker).cloned() }
|
/// Basic engine for an interner that supports recovering if a token is not
|
||||||
|
/// found locally.
|
||||||
|
pub mod local_interner {
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::future;
|
||||||
|
use std::hash::{BuildHasher, Hash};
|
||||||
|
use std::num::NonZeroU64;
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
pub fn by_value<Q: Eq + hash::Hash>(&self, q: &Q) -> Option<Tok<T>>
|
use futures::future::LocalBoxFuture;
|
||||||
where T: Borrow<Q> {
|
use hashbrown::hash_table::{Entry, OccupiedEntry, VacantEntry};
|
||||||
(self.intern.raw_entry())
|
use hashbrown::{DefaultHashBuilder, HashTable};
|
||||||
.from_hash(self.intern.hasher().hash_one(q), |k| k.as_ref().borrow() == q)
|
use orchid_api_traits::Coding;
|
||||||
.map(|p| p.1.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sweep_replica(&mut self) -> Vec<T::Marker> {
|
use super::{IStr, IStrHandle, IStrv, IStrvHandle, InternerSrv};
|
||||||
(self.intern)
|
|
||||||
.extract_if(|k, _| Rc::strong_count(k) == BASE_RC)
|
|
||||||
.map(|(_, v)| {
|
|
||||||
self.by_id.remove(&v.to_api());
|
|
||||||
v.to_api()
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sweep_master(&mut self, retained: HashSet<T::Marker>) {
|
|
||||||
self.intern.retain(|k, v| BASE_RC < Rc::strong_count(k) || retained.contains(&v.to_api()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Interned> Default for Bimap<T> {
|
|
||||||
fn default() -> Self { Self { by_id: HashMap::new(), intern: HashMap::new() } }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait UpComm {
|
|
||||||
fn up<R: Request>(&self, req: R) -> R::Response;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct TypedInterners {
|
|
||||||
strings: Bimap<String>,
|
|
||||||
vecs: Bimap<Vec<Tok<String>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct InternerData {
|
|
||||||
interners: Mutex<TypedInterners>,
|
|
||||||
master: Option<Box<dyn DynRequester<Transfer = api::IntReq>>>,
|
|
||||||
}
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct Interner(Rc<InternerData>);
|
|
||||||
impl Interner {
|
|
||||||
pub fn new_master() -> Self { Self::default() }
|
|
||||||
pub fn new_replica(req: impl DynRequester<Transfer = api::IntReq> + 'static) -> Self {
|
|
||||||
Self(Rc::new(InternerData { master: Some(Box::new(req)), interners: Mutex::default() }))
|
|
||||||
}
|
|
||||||
/// Intern some data; query its identifier if not known locally
|
|
||||||
pub async fn i<T: Interned>(&self, t: &(impl Internable<Interned = T> + ?Sized)) -> Tok<T> {
|
|
||||||
let data = t.get_owned();
|
|
||||||
let mut g = self.0.interners.lock().await;
|
|
||||||
let typed = T::bimap(&mut g);
|
|
||||||
if let Some(tok) = typed.by_value(&data) {
|
|
||||||
return tok;
|
|
||||||
}
|
|
||||||
let marker = match &self.0.master {
|
|
||||||
Some(c) => data.clone().intern(&**c).await,
|
|
||||||
None =>
|
|
||||||
T::Marker::from_id(NonZeroU64::new(ID.fetch_add(1, atomic::Ordering::Relaxed)).unwrap()),
|
|
||||||
};
|
|
||||||
let tok = Tok::new(data, marker);
|
|
||||||
T::bimap(&mut g).insert(tok.clone());
|
|
||||||
tok
|
|
||||||
}
|
|
||||||
/// Extern an identifier; query the data it represents if not known locally
|
|
||||||
pub async fn ex<M: InternMarker>(&self, marker: M) -> Tok<M::Interned> {
|
|
||||||
if let Some(tok) = M::Interned::bimap(&mut *self.0.interners.lock().await).by_marker(marker) {
|
|
||||||
return tok;
|
|
||||||
}
|
|
||||||
assert!(self.0.master.is_some(), "ID not in local interner and this is master");
|
|
||||||
let token = marker.resolve(self).await;
|
|
||||||
M::Interned::bimap(&mut *self.0.interners.lock().await).insert(token.clone());
|
|
||||||
token
|
|
||||||
}
|
|
||||||
pub async fn sweep_replica(&self) -> api::Retained {
|
|
||||||
assert!(self.0.master.is_some(), "Not a replica");
|
|
||||||
let mut g = self.0.interners.lock().await;
|
|
||||||
api::Retained { strings: g.strings.sweep_replica(), vecs: g.vecs.sweep_replica() }
|
|
||||||
}
|
|
||||||
pub async fn sweep_master(&self, retained: api::Retained) {
|
|
||||||
assert!(self.0.master.is_none(), "Not master");
|
|
||||||
let mut g = self.0.interners.lock().await;
|
|
||||||
g.strings.sweep_master(retained.strings.into_iter().collect());
|
|
||||||
g.vecs.sweep_master(retained.vecs.into_iter().collect());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl fmt::Debug for Interner {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "Interner{{ replica: {} }}", self.0.master.is_none())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static ID: atomic::AtomicU64 = atomic::AtomicU64::new(1);
|
|
||||||
|
|
||||||
pub fn merge_retained(into: &mut api::Retained, from: &api::Retained) {
|
|
||||||
into.strings = into.strings.iter().chain(&from.strings).copied().unique().collect();
|
|
||||||
into.vecs = into.vecs.iter().chain(&from.vecs).copied().unique().collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use std::num::NonZero;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use orchid_api_traits::{Decode, enc_vec};
|
|
||||||
use test_executors::spin_on;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::api;
|
use crate::api;
|
||||||
|
|
||||||
#[test]
|
/// Associated types and methods for parallel concepts between scalar and
|
||||||
fn test_i() {
|
/// vector interning
|
||||||
let i = Interner::new_master();
|
pub trait InternableCard: 'static + Sized + Default + Debug {
|
||||||
let _: Tok<String> = spin_on(i.i("foo"));
|
/// API representation of an interner key
|
||||||
let _: Tok<Vec<Tok<String>>> = spin_on(i.i(&[spin_on(i.i("bar")), spin_on(i.i("baz"))]));
|
type Token: Clone + Copy + Debug + Hash + Eq + PartialOrd + Ord + Coding + 'static;
|
||||||
|
/// Owned version of interned value physically held by `'static` interner
|
||||||
|
/// and token
|
||||||
|
type Data: 'static + Borrow<Self::Borrow> + Eq + Hash + Debug;
|
||||||
|
/// Borrowed version of interned value placed in intern queries to avoid a
|
||||||
|
/// copy
|
||||||
|
type Borrow: ToOwned<Owned = Self::Data> + ?Sized + Eq + Hash + Debug;
|
||||||
|
/// Smart object handed out by the interner for storage and comparison in
|
||||||
|
/// third party code. [IStr] or [IStrv]
|
||||||
|
type Interned: Clone + Debug;
|
||||||
|
/// Create smart object from token for fast comparison and a handle for
|
||||||
|
/// everything else incl. virtual drop
|
||||||
|
fn new_interned(token: Self::Token, handle: Rc<Handle<Self>>) -> Self::Interned;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
/// String-specific values for [InternableCard]
|
||||||
fn test_coding() {
|
#[derive(Default, Debug)]
|
||||||
spin_on(async {
|
pub struct StrBranch;
|
||||||
let coded = api::TStr(NonZero::new(3u64).unwrap());
|
impl InternableCard for StrBranch {
|
||||||
let mut enc = &enc_vec(&coded).await[..];
|
type Data = String;
|
||||||
api::TStr::decode(Pin::new(&mut enc)).await;
|
type Token = api::TStr;
|
||||||
assert_eq!(enc, [], "Did not consume all of {enc:?}")
|
type Borrow = str;
|
||||||
})
|
type Interned = IStr;
|
||||||
|
fn new_interned(t: Self::Token, h: Rc<Handle<Self>>) -> Self::Interned { IStr(t, h) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Vector-specific values for [InternableCard]
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct StrvBranch;
|
||||||
|
impl InternableCard for StrvBranch {
|
||||||
|
type Data = Vec<IStr>;
|
||||||
|
type Token = api::TStrv;
|
||||||
|
type Borrow = [IStr];
|
||||||
|
type Interned = IStrv;
|
||||||
|
fn new_interned(t: Self::Token, h: Rc<Handle<Self>>) -> Self::Interned { IStrv(t, h) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pairs interned data with its internment key
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Data<B: InternableCard> {
|
||||||
|
token: B::Token,
|
||||||
|
data: Rc<B::Data>,
|
||||||
|
}
|
||||||
|
impl<B: InternableCard> Clone for Data<B> {
|
||||||
|
fn clone(&self) -> Self { Self { token: self.token, data: self.data.clone() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementor for the trait objects held by [IStr] and [IStrv]
|
||||||
|
pub struct Handle<B: InternableCard> {
|
||||||
|
data: Data<B>,
|
||||||
|
parent: Weak<RefCell<IntData<B>>>,
|
||||||
|
}
|
||||||
|
impl IStrHandle for Handle<StrBranch> {
|
||||||
|
fn rc(&self) -> Rc<String> { self.data.data.clone() }
|
||||||
|
}
|
||||||
|
impl AsRef<str> for Handle<StrBranch> {
|
||||||
|
fn as_ref(&self) -> &str { self.data.data.as_ref().as_ref() }
|
||||||
|
}
|
||||||
|
impl IStrvHandle for Handle<StrvBranch> {
|
||||||
|
fn rc(&self) -> Rc<Vec<IStr>> { self.data.data.clone() }
|
||||||
|
}
|
||||||
|
impl AsRef<[IStr]> for Handle<StrvBranch> {
|
||||||
|
fn as_ref(&self) -> &[IStr] { self.data.data.as_ref().as_ref() }
|
||||||
|
}
|
||||||
|
impl<B: InternableCard> Drop for Handle<B> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let Some(parent) = self.parent.upgrade() else { return };
|
||||||
|
if let Entry::Occupied(ent) =
|
||||||
|
parent.borrow_mut().entry_by_data(self.data.data.as_ref().borrow())
|
||||||
|
{
|
||||||
|
ent.remove();
|
||||||
|
}
|
||||||
|
if let Entry::Occupied(ent) = parent.borrow_mut().entry_by_tok(self.data.token) {
|
||||||
|
ent.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information retained about an interned token indexed both by key and
|
||||||
|
/// value.
|
||||||
|
struct Rec<B: InternableCard> {
|
||||||
|
/// This reference is weak, but the [Drop] handler of [Handle] removes the
|
||||||
|
/// [Rec] from the interner so it is guaranteed to be live.
|
||||||
|
handle: Weak<Handle<B>>,
|
||||||
|
/// Keys for indexing from either table
|
||||||
|
data: Data<B>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read data from an occupied entry in an interner. The equivalent insert
|
||||||
|
/// command is [insert]
|
||||||
|
fn read<B: InternableCard>(entry: OccupiedEntry<'_, Rec<B>>) -> B::Interned {
|
||||||
|
let hand = entry.get().handle.upgrade().expect("Found entry but handle already dropped");
|
||||||
|
B::new_interned(entry.get().data.token, hand)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert some data into an entry borrowed from this same interner.
|
||||||
|
/// The equivalent read command is [read]
|
||||||
|
fn insert<B: InternableCard>(entry: VacantEntry<'_, Rec<B>>, handle: Rc<Handle<B>>) {
|
||||||
|
entry.insert(Rec { data: handle.data.clone(), handle: Rc::downgrade(&handle) });
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct IntData<B: InternableCard> {
|
||||||
|
by_tok: HashTable<Rec<B>>,
|
||||||
|
by_data: HashTable<Rec<B>>,
|
||||||
|
hasher: DefaultHashBuilder,
|
||||||
|
}
|
||||||
|
impl<B: InternableCard> IntData<B> {
|
||||||
|
fn entry_by_data(&mut self, query: &B::Borrow) -> Entry<'_, Rec<B>> {
|
||||||
|
self.by_data.entry(
|
||||||
|
self.hasher.hash_one(query),
|
||||||
|
|rec| rec.data.data.as_ref().borrow() == query,
|
||||||
|
|rec| self.hasher.hash_one(rec.data.data.as_ref().borrow()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn entry_by_tok(&mut self, token: B::Token) -> Entry<'_, Rec<B>> {
|
||||||
|
self.by_tok.entry(
|
||||||
|
self.hasher.hash_one(token),
|
||||||
|
|rec| rec.data.token == token,
|
||||||
|
|rec| self.hasher.hash_one(rec.data.token),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Failing intern command that can be recovered if the value is found
|
||||||
|
/// elsewhere
|
||||||
|
pub struct InternError<'a, B: InternableCard> {
|
||||||
|
int: &'a Int<B>,
|
||||||
|
query: &'a B::Borrow,
|
||||||
|
}
|
||||||
|
impl<B: InternableCard> InternError<'_, B> {
|
||||||
|
/// If a racing write populates the entry, the continuation returns that
|
||||||
|
/// value and discards its argument
|
||||||
|
pub fn set_if_empty(self, token: B::Token) -> B::Interned {
|
||||||
|
let mut int_data = self.int.0.borrow_mut();
|
||||||
|
match int_data.entry_by_data(self.query) {
|
||||||
|
Entry::Occupied(ent) => read(ent),
|
||||||
|
Entry::Vacant(ent) => {
|
||||||
|
let hand = self.int.mk_handle(Data { token, data: Rc::new(self.query.to_owned()) });
|
||||||
|
insert(ent, hand.clone());
|
||||||
|
let Entry::Vacant(other_ent) = int_data.entry_by_tok(token) else {
|
||||||
|
panic!("Data and key tables out of sync")
|
||||||
|
};
|
||||||
|
insert(other_ent, hand.clone());
|
||||||
|
B::new_interned(token, hand)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<B: InternableCard> Debug for InternError<'_, B> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("InternEntry").field(&self.query).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Failing extern command that can be recovered if the value is found
|
||||||
|
/// elsewhere
|
||||||
|
pub struct ExternError<'a, B: InternableCard> {
|
||||||
|
int: &'a Int<B>,
|
||||||
|
token: B::Token,
|
||||||
|
}
|
||||||
|
impl<B: InternableCard> ExternError<'_, B> {
|
||||||
|
/// If a racing write populates the entry, the continuation returns that
|
||||||
|
/// value and discards its argument
|
||||||
|
pub fn set_if_empty(&self, data: Rc<B::Data>) -> B::Interned {
|
||||||
|
let mut int_data = self.int.0.borrow_mut();
|
||||||
|
match int_data.entry_by_tok(self.token) {
|
||||||
|
Entry::Occupied(ent) => read(ent),
|
||||||
|
Entry::Vacant(ent) => {
|
||||||
|
let hand = self.int.mk_handle(Data { token: self.token, data: data.clone() });
|
||||||
|
insert(ent, hand.clone());
|
||||||
|
let Entry::Vacant(other_ent) = int_data.entry_by_data(data.as_ref().borrow()) else {
|
||||||
|
panic!("Data and key tables out of sync")
|
||||||
|
};
|
||||||
|
insert(other_ent, hand.clone());
|
||||||
|
B::new_interned(self.token, hand)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<B: InternableCard> Debug for ExternError<'_, B> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("ExternEntry").field(&self.token).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Int<B: InternableCard>(Rc<RefCell<IntData<B>>>);
|
||||||
|
impl<B: InternableCard> Int<B> {
|
||||||
|
fn mk_handle(&self, data: Data<B>) -> Rc<Handle<B>> {
|
||||||
|
Rc::new(Handle { data: data.clone(), parent: Rc::downgrade(&self.0.clone()) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look up by value, or yield to figure out its ID from elsewhere
|
||||||
|
pub fn i<'a>(&'a self, query: &'a B::Borrow) -> Result<B::Interned, InternError<'a, B>> {
|
||||||
|
if let Entry::Occupied(val) = self.0.borrow_mut().entry_by_data(query) {
|
||||||
|
return Ok(read(val));
|
||||||
|
}
|
||||||
|
Err(InternError { int: self, query })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look up by key or yield to figure out its value from elsewhere
|
||||||
|
pub fn e(&self, token: B::Token) -> Result<B::Interned, ExternError<'_, B>> {
|
||||||
|
if let Entry::Occupied(ent) = self.0.borrow_mut().entry_by_tok(token) {
|
||||||
|
return Ok(read(ent));
|
||||||
|
}
|
||||||
|
Err(ExternError { int: self, token })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static NEXT_ID: RefCell<u64> = 0.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_new_id<T>(fun: impl FnOnce(NonZeroU64) -> T) -> T {
|
||||||
|
fun(
|
||||||
|
NonZeroU64::new(NEXT_ID.with_borrow_mut(|id| {
|
||||||
|
*id += 1;
|
||||||
|
*id
|
||||||
|
}))
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct LocalInterner {
|
||||||
|
str: Int<StrBranch>,
|
||||||
|
strv: Int<StrvBranch>,
|
||||||
|
}
|
||||||
|
impl InternerSrv for LocalInterner {
|
||||||
|
fn is<'a>(&'a self, v: &'a str) -> LocalBoxFuture<'a, IStr> {
|
||||||
|
match self.str.i(v) {
|
||||||
|
Ok(int) => Box::pin(future::ready(int)),
|
||||||
|
Err(e) => with_new_id(|id| Box::pin(future::ready(e.set_if_empty(api::TStr(id))))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, IStr> {
|
||||||
|
Box::pin(future::ready(self.str.e(t).expect("Unrecognized token cannot be externed")))
|
||||||
|
}
|
||||||
|
fn iv<'a>(&'a self, v: &'a [IStr]) -> LocalBoxFuture<'a, IStrv> {
|
||||||
|
match self.strv.i(v) {
|
||||||
|
Ok(int) => Box::pin(future::ready(int)),
|
||||||
|
Err(e) => with_new_id(|id| Box::pin(future::ready(e.set_if_empty(api::TStrv(id))))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn ev(&self, t: orchid_api::TStrv) -> LocalBoxFuture<'_, IStrv> {
|
||||||
|
Box::pin(future::ready(self.strv.e(t).expect("Unrecognized token cannot be externed")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a basic thread-local interner for testing and root role.
|
||||||
|
pub fn local_interner() -> Rc<dyn InternerSrv> { Rc::<LocalInterner>::default() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ impl<'a, I: Iterator<Item = E> + Clone, E: fmt::Display> fmt::Display for PrintL
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait IteratorPrint: Iterator<Item: fmt::Display> + Clone {
|
pub trait IteratorPrint: Iterator<Item: fmt::Display> + Clone {
|
||||||
|
/// Pretty-print a list with a comma-separated enumeration and an operator
|
||||||
|
/// word (such as "and" or "or") inserted before the last
|
||||||
fn display<'a>(self, operator: &'a str) -> PrintList<'a, Self, Self::Item> {
|
fn display<'a>(self, operator: &'a str) -> PrintList<'a, Self, Self::Item> {
|
||||||
PrintList(self, operator)
|
PrintList(self, operator)
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,44 @@
|
|||||||
pub use async_once_cell;
|
pub use async_once_cell;
|
||||||
use orchid_api as api;
|
use orchid_api as api;
|
||||||
|
|
||||||
pub mod box_cow;
|
mod on_drop;
|
||||||
pub mod boxed_iter;
|
pub use on_drop::*;
|
||||||
pub mod builtin;
|
mod binary;
|
||||||
pub mod char_filter;
|
pub use binary::*;
|
||||||
|
mod id_store;
|
||||||
|
pub use id_store::*;
|
||||||
|
mod boxed_iter;
|
||||||
|
pub use boxed_iter::*;
|
||||||
|
mod char_filter;
|
||||||
|
pub use char_filter::*;
|
||||||
pub mod clone;
|
pub mod clone;
|
||||||
pub mod combine;
|
mod error;
|
||||||
pub mod error;
|
pub use error::*;
|
||||||
pub mod event;
|
mod format;
|
||||||
pub mod format;
|
pub use format::*;
|
||||||
pub mod id_store;
|
mod interner;
|
||||||
pub mod interner;
|
pub use interner::*;
|
||||||
pub mod iter_utils;
|
mod iter_print;
|
||||||
pub mod join;
|
pub use iter_print::*;
|
||||||
pub mod location;
|
mod join;
|
||||||
pub mod logging;
|
pub use join::*;
|
||||||
|
mod location;
|
||||||
|
pub use location::*;
|
||||||
|
mod logging;
|
||||||
|
pub use logging::*;
|
||||||
mod match_mapping;
|
mod match_mapping;
|
||||||
pub mod msg;
|
mod name;
|
||||||
pub mod name;
|
pub use name::*;
|
||||||
pub mod number;
|
mod number;
|
||||||
pub mod parse;
|
pub use number::*;
|
||||||
pub mod pure_seq;
|
mod parse;
|
||||||
pub mod reqnot;
|
pub use parse::*;
|
||||||
pub mod sequence;
|
mod comm;
|
||||||
pub mod side;
|
pub use comm::*;
|
||||||
|
mod side;
|
||||||
|
pub use side::*;
|
||||||
|
mod stash;
|
||||||
|
pub use stash::*;
|
||||||
mod tl_cache;
|
mod tl_cache;
|
||||||
pub mod tokens;
|
mod tree;
|
||||||
pub mod tree;
|
pub use tree::*;
|
||||||
|
|||||||
@@ -7,25 +7,34 @@ use std::ops::{Add, AddAssign, Range};
|
|||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use trait_set::trait_set;
|
use trait_set::trait_set;
|
||||||
|
|
||||||
use crate::error::ErrPos;
|
use crate::{ErrPos, IStr, IteratorPrint, Sym, api, es, is, match_mapping, sym};
|
||||||
use crate::interner::{Interner, Tok};
|
|
||||||
use crate::name::Sym;
|
|
||||||
use crate::{api, match_mapping, sym};
|
|
||||||
|
|
||||||
trait_set! {
|
trait_set! {
|
||||||
pub trait GetSrc = FnMut(&Sym) -> Tok<String>;
|
pub trait GetSrc = FnMut(&Sym) -> IStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One or more positions in code that are associated with an event, value, or
|
||||||
|
/// other consequence of that code
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Pos {
|
pub enum Pos {
|
||||||
|
/// Location not known, for example because the expression was decoded from a
|
||||||
|
/// source that doesn't have a meaningful location attached, from a format
|
||||||
|
/// that does not encode location data
|
||||||
None,
|
None,
|
||||||
|
/// If the expression in question is a slot, it receives the substituted
|
||||||
|
/// expression's position. If the expression is being placed into a slot, this
|
||||||
|
/// is discarded. In all other cases, it is a conflict and an error
|
||||||
SlotTarget,
|
SlotTarget,
|
||||||
/// Used in functions to denote the generated code that carries on the
|
/// Used in functions to denote the generated code that carries on the
|
||||||
/// location of the call. Not allowed in the const tree.
|
/// location of the call. Not allowed in the const tree
|
||||||
Inherit,
|
Inherit,
|
||||||
|
/// ID and parameters of a generator (such as an extension)
|
||||||
Gen(CodeGenInfo),
|
Gen(CodeGenInfo),
|
||||||
/// Range and file
|
/// Range and file
|
||||||
SrcRange(SrcRange),
|
SrcRange(SrcRange),
|
||||||
|
/// More than one positions. This vec should not contain another [Pos::Multi]
|
||||||
|
/// and should be `>=2` long. To ensure this, use `+` and `+=` to combine
|
||||||
|
/// positions and do not construct this directly.
|
||||||
Multi(Vec<Pos>),
|
Multi(Vec<Pos>),
|
||||||
}
|
}
|
||||||
impl Pos {
|
impl Pos {
|
||||||
@@ -33,17 +42,18 @@ impl Pos {
|
|||||||
match self {
|
match self {
|
||||||
Self::Gen(g) => g.to_string(),
|
Self::Gen(g) => g.to_string(),
|
||||||
Self::SrcRange(sr) => sr.pretty_print(&get_src(&sr.path)),
|
Self::SrcRange(sr) => sr.pretty_print(&get_src(&sr.path)),
|
||||||
|
Self::Multi(posv) => posv.iter().display("and").to_string(),
|
||||||
// Can't pretty print partial and meta-location
|
// Can't pretty print partial and meta-location
|
||||||
other => format!("{other:?}"),
|
other => format!("{other:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn from_api(api: &api::Location, i: &Interner) -> Self {
|
pub async fn from_api(api: &api::Location) -> Self {
|
||||||
match_mapping!(api, api::Location => Pos {
|
match_mapping!(api, api::Location => Pos {
|
||||||
None, Inherit, SlotTarget,
|
None, Inherit, SlotTarget,
|
||||||
Gen(cgi => CodeGenInfo::from_api(cgi, i).await),
|
Gen(cgi => CodeGenInfo::from_api(cgi).await),
|
||||||
Multi(v => join_all(v.iter().map(|l| Pos::from_api(l, i))).await)
|
Multi(v => join_all(v.iter().map(Pos::from_api)).await)
|
||||||
} {
|
} {
|
||||||
api::Location::SourceRange(sr) => Self::SrcRange(SrcRange::from_api(sr, i).await)
|
api::Location::SourceRange(sr) => Self::SrcRange(SrcRange::from_api(sr).await)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn to_api(&self) -> api::Location {
|
pub fn to_api(&self) -> api::Location {
|
||||||
@@ -106,9 +116,9 @@ impl SrcRange {
|
|||||||
pub fn new(range: Range<u32>, path: &Sym) -> Self {
|
pub fn new(range: Range<u32>, path: &Sym) -> Self {
|
||||||
Self { range: range.clone(), path: path.clone() }
|
Self { range: range.clone(), path: path.clone() }
|
||||||
}
|
}
|
||||||
/// Create a dud [SourceRange] for testing. Its value is unspecified and
|
/// Create a dud [SrcRange] for testing. Its value is unspecified and
|
||||||
/// volatile.
|
/// volatile.
|
||||||
pub async fn mock(i: &Interner) -> Self { Self { range: 0..1, path: sym!(test; i) } }
|
pub async fn mock() -> Self { Self { range: 0..1, path: sym!(test) } }
|
||||||
/// Path the source text was loaded from
|
/// Path the source text was loaded from
|
||||||
pub fn path(&self) -> Sym { self.path.clone() }
|
pub fn path(&self) -> Sym { self.path.clone() }
|
||||||
/// Byte range
|
/// Byte range
|
||||||
@@ -123,6 +133,10 @@ impl SrcRange {
|
|||||||
pub fn map_range(&self, map: impl FnOnce(Range<u32>) -> Range<u32>) -> Self {
|
pub fn map_range(&self, map: impl FnOnce(Range<u32>) -> Range<u32>) -> Self {
|
||||||
Self { range: map(self.range()), path: self.path() }
|
Self { range: map(self.range()), path: self.path() }
|
||||||
}
|
}
|
||||||
|
/// Format the range in a way that VSCode can convert to a link and is
|
||||||
|
/// human-readable. For this operation we need the source code text, but
|
||||||
|
/// holding it in the position object is a bit heavy so instead we take it as
|
||||||
|
/// an argument
|
||||||
pub fn pretty_print(&self, src: &str) -> String {
|
pub fn pretty_print(&self, src: &str) -> String {
|
||||||
let (sl, sc) = pos2lc(src, self.range.start);
|
let (sl, sc) = pos2lc(src, self.range.start);
|
||||||
let (el, ec) = pos2lc(src, self.range.end);
|
let (el, ec) = pos2lc(src, self.range.end);
|
||||||
@@ -132,13 +146,21 @@ impl SrcRange {
|
|||||||
(false, _) => format!("{sl}:{sc}..{el}:{ec}"),
|
(false, _) => format!("{sl}:{sc}..{el}:{ec}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Zero-width position at a certain offset
|
||||||
pub fn zw(path: Sym, pos: u32) -> Self { Self { path, range: pos..pos } }
|
pub fn zw(path: Sym, pos: u32) -> Self { Self { path, range: pos..pos } }
|
||||||
pub async fn from_api(api: &api::SourceRange, i: &Interner) -> Self {
|
/// Deserialize from a message
|
||||||
Self { path: Sym::from_api(api.path, i).await, range: api.range.clone() }
|
pub async fn from_api(api: &api::SourceRange) -> Self {
|
||||||
|
Self { path: Sym::from_api(api.path).await, range: api.range.clone() }
|
||||||
}
|
}
|
||||||
|
/// Serialize to a message
|
||||||
pub fn to_api(&self) -> api::SourceRange {
|
pub fn to_api(&self) -> api::SourceRange {
|
||||||
api::SourceRange { path: self.path.to_api(), range: self.range.clone() }
|
api::SourceRange { path: self.path.to_api(), range: self.range.clone() }
|
||||||
}
|
}
|
||||||
|
/// Connect two ranges into one
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// if the ranges are not from the same file
|
||||||
pub fn to(&self, rhs: &Self) -> Self {
|
pub fn to(&self, rhs: &Self) -> Self {
|
||||||
assert_eq!(self.path, rhs.path, "Range continues across files");
|
assert_eq!(self.path, rhs.path, "Range continues across files");
|
||||||
Self { path: self.path(), range: self.start().min(rhs.start())..self.end().max(rhs.end()) }
|
Self { path: self.path(), range: self.start().min(rhs.start())..self.end().max(rhs.end()) }
|
||||||
@@ -162,25 +184,22 @@ pub struct CodeGenInfo {
|
|||||||
/// formatted like a Rust namespace
|
/// formatted like a Rust namespace
|
||||||
pub generator: Sym,
|
pub generator: Sym,
|
||||||
/// Unformatted user message with relevant circumstances and parameters
|
/// Unformatted user message with relevant circumstances and parameters
|
||||||
pub details: Tok<String>,
|
pub details: IStr,
|
||||||
}
|
}
|
||||||
impl CodeGenInfo {
|
impl CodeGenInfo {
|
||||||
/// A codegen marker with no user message and parameters
|
/// A codegen marker with no user message and parameters
|
||||||
pub async fn new_short(generator: Sym, i: &Interner) -> Self {
|
pub async fn new_short(generator: Sym) -> Self { Self { generator, details: is("").await } }
|
||||||
Self { generator, details: i.i("").await }
|
|
||||||
}
|
|
||||||
/// A codegen marker with a user message or parameters
|
/// A codegen marker with a user message or parameters
|
||||||
pub async fn new_details(generator: Sym, details: impl AsRef<str>, i: &Interner) -> Self {
|
pub async fn new_details(generator: Sym, details: impl AsRef<str>) -> Self {
|
||||||
Self { generator, details: i.i(details.as_ref()).await }
|
Self { generator, details: is(details.as_ref()).await }
|
||||||
}
|
}
|
||||||
/// Syntactic location
|
/// Syntactic location
|
||||||
pub fn pos(&self) -> Pos { Pos::Gen(self.clone()) }
|
pub fn pos(&self) -> Pos { Pos::Gen(self.clone()) }
|
||||||
pub async fn from_api(api: &api::CodeGenInfo, i: &Interner) -> Self {
|
/// Deserialize from a message
|
||||||
Self {
|
pub async fn from_api(api: &api::CodeGenInfo) -> Self {
|
||||||
generator: Sym::from_api(api.generator, i).await,
|
Self { generator: Sym::from_api(api.generator).await, details: es(api.details).await }
|
||||||
details: Tok::from_api(api.details, i).await,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
/// Serialize to a message
|
||||||
pub fn to_api(&self) -> api::CodeGenInfo {
|
pub fn to_api(&self) -> api::CodeGenInfo {
|
||||||
api::CodeGenInfo { generator: self.generator.to_api(), details: self.details.to_api() }
|
api::CodeGenInfo { generator: self.generator.to_api(), details: self.details.to_api() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,76 @@
|
|||||||
|
use std::any::Any;
|
||||||
use std::fmt::Arguments;
|
use std::fmt::Arguments;
|
||||||
use std::fs::File;
|
use std::rc::Rc;
|
||||||
use std::io::{Write, stderr};
|
|
||||||
|
|
||||||
pub use api::LogStrategy;
|
use futures::future::LocalBoxFuture;
|
||||||
use itertools::Itertools;
|
use task_local::task_local;
|
||||||
|
|
||||||
use crate::api;
|
use crate::{api, clone};
|
||||||
|
|
||||||
#[derive(Clone)]
|
/// A first argument to [write!] and [writeln!] that causes them to return a
|
||||||
pub struct Logger(api::LogStrategy);
|
/// future.
|
||||||
impl Logger {
|
pub trait LogWriter {
|
||||||
pub fn new(strat: api::LogStrategy) -> Self { Self(strat) }
|
fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()>;
|
||||||
pub fn log(&self, msg: impl AsRef<str>) { writeln!(self, "{}", msg.as_ref()) }
|
}
|
||||||
pub fn strat(&self) -> api::LogStrategy { self.0.clone() }
|
|
||||||
pub fn log_buf(&self, event: impl AsRef<str>, buf: &[u8]) {
|
/// Injectable logging service passed to [with_logger]
|
||||||
if std::env::var("ORCHID_LOG_BUFFERS").is_ok_and(|v| !v.is_empty()) {
|
pub trait Logger: Any {
|
||||||
writeln!(self, "{}: [{}]", event.as_ref(), buf.iter().map(|b| format!("{b:02x}")).join(" "))
|
/// Obtain a writer that processes the message according to the given category
|
||||||
}
|
fn writer(&self, category: &str) -> Rc<dyn LogWriter>;
|
||||||
}
|
/// Obtain a serializable limited description of what will eventually happen
|
||||||
pub fn write_fmt(&self, fmt: Arguments) {
|
/// to the message
|
||||||
match &self.0 {
|
fn strat(&self, category: &str) -> api::LogStrategy;
|
||||||
api::LogStrategy::Discard => (),
|
/// Determine whether a certain category would get processed at all. This is a
|
||||||
api::LogStrategy::StdErr => {
|
/// shortcut
|
||||||
stderr().write_fmt(fmt).expect("Could not write to stderr!");
|
fn is_active(&self, category: &str) -> bool {
|
||||||
stderr().flush().expect("Could not flush stderr")
|
!matches!(self.strat(category), api::LogStrategy::Discard)
|
||||||
},
|
|
||||||
api::LogStrategy::File(f) => {
|
|
||||||
let mut file = (File::options().write(true).create(true).truncate(true).open(f))
|
|
||||||
.expect("Could not open logfile");
|
|
||||||
file.write_fmt(fmt).expect("Could not write to logfile");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task_local! {
|
||||||
|
static LOGGER: Rc<dyn Logger>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Within the future passed to this function the freestanding [log] and
|
||||||
|
/// [get_logger] functions can be used
|
||||||
|
pub async fn with_logger<F: Future>(logger: impl Logger + 'static, fut: F) -> F::Output {
|
||||||
|
LOGGER.scope(Rc::new(logger), fut).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain an async log writer
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use orchid_base::log;
|
||||||
|
/// async {
|
||||||
|
/// let user = "lorentz";
|
||||||
|
/// writeln!(log("info"), "Hello from {user}").await
|
||||||
|
/// };
|
||||||
|
/// ```
|
||||||
|
pub fn log(category: &str) -> Rc<dyn LogWriter> {
|
||||||
|
LOGGER.try_with(|l| l.writer(category)).expect("Logger not set!")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain a reference to the current [Logger]. This is mostly useful for
|
||||||
|
/// [Logger::is_active]-based optimizations
|
||||||
|
pub fn get_logger() -> Rc<dyn Logger> { LOGGER.try_with(|l| l.clone()).expect("Logger not set!") }
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TestLogger(Rc<dyn Fn(String) -> LocalBoxFuture<'static, ()>>);
|
||||||
|
impl LogWriter for TestLogger {
|
||||||
|
fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()> {
|
||||||
|
(self.0)(fmt.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Logger for TestLogger {
|
||||||
|
fn strat(&self, _category: &str) -> orchid_api::LogStrategy { orchid_api::LogStrategy::Default }
|
||||||
|
fn writer(&self, _category: &str) -> std::rc::Rc<dyn LogWriter> { Rc::new(self.clone()) }
|
||||||
|
}
|
||||||
|
impl TestLogger {
|
||||||
|
pub fn new(f: impl AsyncFn(String) + 'static) -> Self {
|
||||||
|
let f = Rc::new(f);
|
||||||
|
Self(Rc::new(move |s| clone!(f; Box::pin(async move { f(s).await }))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for TestLogger {
|
||||||
|
fn default() -> Self { TestLogger::new(async |s| eprint!("{s}")) }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
use std::io;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use futures::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
|
||||||
use orchid_api_traits::{Decode, Encode};
|
|
||||||
|
|
||||||
pub async fn send_msg(mut write: Pin<&mut impl AsyncWrite>, msg: &[u8]) -> io::Result<()> {
|
|
||||||
let mut len_buf = vec![];
|
|
||||||
u32::try_from(msg.len()).unwrap().encode(Pin::new(&mut len_buf)).await;
|
|
||||||
write.write_all(&len_buf).await?;
|
|
||||||
write.write_all(msg).await?;
|
|
||||||
write.flush().await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn recv_msg(mut read: Pin<&mut impl AsyncRead>) -> io::Result<Vec<u8>> {
|
|
||||||
let mut len_buf = [0u8; (u32::BITS / 8) as usize];
|
|
||||||
read.read_exact(&mut len_buf).await?;
|
|
||||||
let len = u32::decode(Pin::new(&mut &len_buf[..])).await;
|
|
||||||
let mut msg = vec![0u8; len as usize];
|
|
||||||
read.read_exact(&mut msg).await?;
|
|
||||||
Ok(msg)
|
|
||||||
}
|
|
||||||
@@ -11,66 +11,60 @@ use futures::future::{OptionFuture, join_all};
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use trait_set::trait_set;
|
use trait_set::trait_set;
|
||||||
|
|
||||||
use crate::api;
|
use crate::{IStr, IStrv, api, es, ev, is, iv};
|
||||||
use crate::interner::{InternMarker, Interner, Tok};
|
|
||||||
|
|
||||||
trait_set! {
|
trait_set! {
|
||||||
/// Traits that all name iterators should implement
|
/// Traits that all name iterators should implement
|
||||||
pub trait NameIter = Iterator<Item = Tok<String>> + DoubleEndedIterator + ExactSizeIterator;
|
pub trait NameIter = Iterator<Item = IStr> + DoubleEndedIterator + ExactSizeIterator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A token path which may be empty. [VName] is the non-empty version
|
/// A token path which may be empty. [VName] is the non-empty version
|
||||||
#[derive(Clone, Default, Hash, PartialEq, Eq)]
|
#[derive(Clone, Default, Hash, PartialEq, Eq)]
|
||||||
pub struct VPath(Vec<Tok<String>>);
|
pub struct VPath(Vec<IStr>);
|
||||||
impl VPath {
|
impl VPath {
|
||||||
/// Collect segments into a vector
|
/// Collect segments into a vector
|
||||||
pub fn new(items: impl IntoIterator<Item = Tok<String>>) -> Self {
|
pub fn new(items: impl IntoIterator<Item = IStr>) -> Self { Self(items.into_iter().collect()) }
|
||||||
Self(items.into_iter().collect())
|
|
||||||
}
|
|
||||||
/// Number of path segments
|
/// Number of path segments
|
||||||
pub fn len(&self) -> usize { self.0.len() }
|
pub fn len(&self) -> usize { self.0.len() }
|
||||||
/// Whether there are any path segments. In other words, whether this is a
|
/// Whether there are any path segments. In other words, whether this is a
|
||||||
/// valid name
|
/// valid name
|
||||||
pub fn is_empty(&self) -> bool { self.len() == 0 }
|
pub fn is_empty(&self) -> bool { self.len() == 0 }
|
||||||
/// Prepend some tokens to the path
|
/// Prepend some tokens to the path
|
||||||
pub fn prefix(self, items: impl IntoIterator<Item = Tok<String>>) -> Self {
|
pub fn prefix(self, items: impl IntoIterator<Item = IStr>) -> Self {
|
||||||
Self(items.into_iter().chain(self.0).collect())
|
Self(items.into_iter().chain(self.0).collect())
|
||||||
}
|
}
|
||||||
/// Append some tokens to the path
|
/// Append some tokens to the path
|
||||||
pub fn suffix(self, items: impl IntoIterator<Item = Tok<String>>) -> Self {
|
pub fn suffix(self, items: impl IntoIterator<Item = IStr>) -> Self {
|
||||||
Self(self.0.into_iter().chain(items).collect())
|
Self(self.0.into_iter().chain(items).collect())
|
||||||
}
|
}
|
||||||
/// Partition the string by `::` namespace separators
|
/// Partition the string by `::` namespace separators
|
||||||
pub async fn parse(s: &str, i: &Interner) -> Self {
|
pub async fn parse(s: &str) -> Self {
|
||||||
Self(if s.is_empty() { vec![] } else { join_all(s.split("::").map(|s| i.i(s))).await })
|
Self(if s.is_empty() { vec![] } else { join_all(s.split("::").map(is)).await })
|
||||||
}
|
}
|
||||||
/// Walk over the segments
|
/// Walk over the segments
|
||||||
pub fn str_iter(&self) -> impl Iterator<Item = &'_ str> {
|
pub fn str_iter(&self) -> impl Iterator<Item = &'_ str> { Box::new(self.0.iter().map(|s| &**s)) }
|
||||||
Box::new(self.0.iter().map(|s| s.as_str()))
|
|
||||||
}
|
|
||||||
/// Try to convert into non-empty version
|
/// Try to convert into non-empty version
|
||||||
pub fn into_name(self) -> Result<VName, EmptyNameError> { VName::new(self.0) }
|
pub fn into_name(self) -> Result<VName, EmptyNameError> { VName::new(self.0) }
|
||||||
/// Add a token to the path. Since now we know that it can't be empty, turn it
|
/// Add a token to the path. Since now we know that it can't be empty, turn it
|
||||||
/// into a name.
|
/// into a name.
|
||||||
pub fn name_with_suffix(self, name: Tok<String>) -> VName {
|
pub fn name_with_suffix(self, name: IStr) -> VName {
|
||||||
VName(self.into_iter().chain([name]).collect())
|
VName(self.into_iter().chain([name]).collect())
|
||||||
}
|
}
|
||||||
/// Add a token to the beginning of the. Since now we know that it can't be
|
/// Add a token to the beginning of the. Since now we know that it can't be
|
||||||
/// empty, turn it into a name.
|
/// empty, turn it into a name.
|
||||||
pub fn name_with_prefix(self, name: Tok<String>) -> VName {
|
pub fn name_with_prefix(self, name: IStr) -> VName {
|
||||||
VName([name].into_iter().chain(self).collect())
|
VName([name].into_iter().chain(self).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a fs path to a vpath
|
/// Convert a fs path to a vpath
|
||||||
pub async fn from_path(path: &Path, ext: &str, i: &Interner) -> Option<(Self, bool)> {
|
pub async fn from_path(path: &Path, ext: &str) -> Option<(Self, bool)> {
|
||||||
async fn to_vpath(p: &Path, i: &Interner) -> Option<VPath> {
|
async fn to_vpath(p: &Path) -> Option<VPath> {
|
||||||
let tok_opt_v =
|
let tok_opt_v = join_all(p.iter().map(|c| OptionFuture::from(c.to_str().map(is)))).await;
|
||||||
join_all(p.iter().map(|c| OptionFuture::from(c.to_str().map(|s| i.i(s))))).await;
|
|
||||||
tok_opt_v.into_iter().collect::<Option<_>>().map(VPath)
|
tok_opt_v.into_iter().collect::<Option<_>>().map(VPath)
|
||||||
}
|
}
|
||||||
match path.extension().map(|s| s.to_str()) {
|
match path.extension().map(|s| s.to_str()) {
|
||||||
Some(Some(s)) if s == ext => Some((to_vpath(&path.with_extension(""), i).await?, true)),
|
Some(Some(s)) if s == ext => Some((to_vpath(&path.with_extension("")).await?, true)),
|
||||||
None => Some((to_vpath(path, i).await?, false)),
|
None => Some((to_vpath(path).await?, false)),
|
||||||
Some(_) => None,
|
Some(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,30 +77,28 @@ impl fmt::Display for VPath {
|
|||||||
write!(f, "{}", self.str_iter().join("::"))
|
write!(f, "{}", self.str_iter().join("::"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl FromIterator<Tok<String>> for VPath {
|
impl FromIterator<IStr> for VPath {
|
||||||
fn from_iter<T: IntoIterator<Item = Tok<String>>>(iter: T) -> Self {
|
fn from_iter<T: IntoIterator<Item = IStr>>(iter: T) -> Self { Self(iter.into_iter().collect()) }
|
||||||
Self(iter.into_iter().collect())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl IntoIterator for VPath {
|
impl IntoIterator for VPath {
|
||||||
type Item = Tok<String>;
|
type Item = IStr;
|
||||||
type IntoIter = vec::IntoIter<Self::Item>;
|
type IntoIter = vec::IntoIter<Self::Item>;
|
||||||
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
|
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
|
||||||
}
|
}
|
||||||
impl Borrow<[Tok<String>]> for VPath {
|
impl Borrow<[IStr]> for VPath {
|
||||||
fn borrow(&self) -> &[Tok<String>] { &self.0[..] }
|
fn borrow(&self) -> &[IStr] { &self.0[..] }
|
||||||
}
|
}
|
||||||
impl Deref for VPath {
|
impl Deref for VPath {
|
||||||
type Target = [Tok<String>];
|
type Target = [IStr];
|
||||||
fn deref(&self) -> &Self::Target { self.borrow() }
|
fn deref(&self) -> &Self::Target { self.borrow() }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Index<T> for VPath
|
impl<T> Index<T> for VPath
|
||||||
where [Tok<String>]: Index<T>
|
where [IStr]: Index<T>
|
||||||
{
|
{
|
||||||
type Output = <[Tok<String>] as Index<T>>::Output;
|
type Output = <[IStr] as Index<T>>::Output;
|
||||||
|
|
||||||
fn index(&self, index: T) -> &Self::Output { &Borrow::<[Tok<String>]>::borrow(self)[index] }
|
fn index(&self, index: T) -> &Self::Output { &Borrow::<[IStr]>::borrow(self)[index] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mutable representation of a namespaced identifier of at least one segment.
|
/// A mutable representation of a namespaced identifier of at least one segment.
|
||||||
@@ -116,50 +108,42 @@ where [Tok<String>]: Index<T>
|
|||||||
/// See also [Sym] for the immutable representation, and [VPath] for possibly
|
/// See also [Sym] for the immutable representation, and [VPath] for possibly
|
||||||
/// empty values
|
/// empty values
|
||||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||||
pub struct VName(Vec<Tok<String>>);
|
pub struct VName(Vec<IStr>);
|
||||||
impl VName {
|
impl VName {
|
||||||
/// Assert that the sequence isn't empty and wrap it in [VName] to represent
|
/// Assert that the sequence isn't empty and wrap it in [VName] to represent
|
||||||
/// this invariant
|
/// this invariant
|
||||||
pub fn new(items: impl IntoIterator<Item = Tok<String>>) -> Result<Self, EmptyNameError> {
|
pub fn new(items: impl IntoIterator<Item = IStr>) -> Result<Self, EmptyNameError> {
|
||||||
let data: Vec<_> = items.into_iter().collect();
|
let data: Vec<_> = items.into_iter().collect();
|
||||||
if data.is_empty() { Err(EmptyNameError) } else { Ok(Self(data)) }
|
if data.is_empty() { Err(EmptyNameError) } else { Ok(Self(data)) }
|
||||||
}
|
}
|
||||||
pub async fn deintern(
|
pub async fn deintern(name: impl IntoIterator<Item = api::TStr>) -> Result<Self, EmptyNameError> {
|
||||||
name: impl IntoIterator<Item = api::TStr>,
|
Self::new(join_all(name.into_iter().map(es)).await)
|
||||||
i: &Interner,
|
|
||||||
) -> Result<Self, EmptyNameError> {
|
|
||||||
Self::new(join_all(name.into_iter().map(|m| Tok::from_api(m, i))).await)
|
|
||||||
}
|
}
|
||||||
/// Unwrap the enclosed vector
|
/// Unwrap the enclosed vector
|
||||||
pub fn into_vec(self) -> Vec<Tok<String>> { self.0 }
|
pub fn into_vec(self) -> Vec<IStr> { self.0 }
|
||||||
/// Get a reference to the enclosed vector
|
/// Get a reference to the enclosed vector
|
||||||
pub fn vec(&self) -> &Vec<Tok<String>> { &self.0 }
|
pub fn vec(&self) -> &Vec<IStr> { &self.0 }
|
||||||
/// Mutable access to the underlying vector. To ensure correct results, this
|
/// Mutable access to the underlying vector. To ensure correct results, this
|
||||||
/// must never be empty.
|
/// must never be empty.
|
||||||
pub fn vec_mut(&mut self) -> &mut Vec<Tok<String>> { &mut self.0 }
|
pub fn vec_mut(&mut self) -> &mut Vec<IStr> { &mut self.0 }
|
||||||
/// Intern the name and return a [Sym]
|
/// Intern the name and return a [Sym]
|
||||||
pub async fn to_sym(&self, i: &Interner) -> Sym { Sym(i.i(&self.0[..]).await) }
|
pub async fn to_sym(&self) -> Sym { Sym(iv(&self.0[..]).await) }
|
||||||
/// If this name has only one segment, return it
|
/// If this name has only one segment, return it
|
||||||
pub fn as_root(&self) -> Option<Tok<String>> { self.0.iter().exactly_one().ok().cloned() }
|
pub fn as_root(&self) -> Option<IStr> { self.0.iter().exactly_one().ok().cloned() }
|
||||||
/// Prepend the segments to this name
|
/// Prepend the segments to this name
|
||||||
#[must_use = "This is a pure function"]
|
#[must_use = "This is a pure function"]
|
||||||
pub fn prefix(self, items: impl IntoIterator<Item = Tok<String>>) -> Self {
|
pub fn prefix(self, items: impl IntoIterator<Item = IStr>) -> Self {
|
||||||
Self(items.into_iter().chain(self.0).collect())
|
Self(items.into_iter().chain(self.0).collect())
|
||||||
}
|
}
|
||||||
/// Append the segments to this name
|
/// Append the segments to this name
|
||||||
#[must_use = "This is a pure function"]
|
#[must_use = "This is a pure function"]
|
||||||
pub fn suffix(self, items: impl IntoIterator<Item = Tok<String>>) -> Self {
|
pub fn suffix(self, items: impl IntoIterator<Item = IStr>) -> Self {
|
||||||
Self(self.0.into_iter().chain(items).collect())
|
Self(self.0.into_iter().chain(items).collect())
|
||||||
}
|
}
|
||||||
/// Read a `::` separated namespaced name
|
/// Read a `::` separated namespaced name
|
||||||
pub async fn parse(s: &str, i: &Interner) -> Result<Self, EmptyNameError> {
|
pub async fn parse(s: &str) -> Result<Self, EmptyNameError> { Self::new(VPath::parse(s).await) }
|
||||||
Self::new(VPath::parse(s, i).await)
|
|
||||||
}
|
|
||||||
pub async fn literal(s: &'static str, i: &Interner) -> Self {
|
|
||||||
Self::parse(s, i).await.expect("empty literal !?")
|
|
||||||
}
|
|
||||||
/// Obtain an iterator over the segments of the name
|
/// Obtain an iterator over the segments of the name
|
||||||
pub fn iter(&self) -> impl Iterator<Item = Tok<String>> + '_ { self.0.iter().cloned() }
|
pub fn iter(&self) -> impl Iterator<Item = IStr> + '_ { self.0.iter().cloned() }
|
||||||
}
|
}
|
||||||
impl fmt::Debug for VName {
|
impl fmt::Debug for VName {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "VName({self})") }
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "VName({self})") }
|
||||||
@@ -170,22 +154,22 @@ impl fmt::Display for VName {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl IntoIterator for VName {
|
impl IntoIterator for VName {
|
||||||
type Item = Tok<String>;
|
type Item = IStr;
|
||||||
type IntoIter = vec::IntoIter<Self::Item>;
|
type IntoIter = vec::IntoIter<Self::Item>;
|
||||||
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
|
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
|
||||||
}
|
}
|
||||||
impl<T> Index<T> for VName
|
impl<T> Index<T> for VName
|
||||||
where [Tok<String>]: Index<T>
|
where [IStr]: Index<T>
|
||||||
{
|
{
|
||||||
type Output = <[Tok<String>] as Index<T>>::Output;
|
type Output = <[IStr] as Index<T>>::Output;
|
||||||
|
|
||||||
fn index(&self, index: T) -> &Self::Output { &self.deref()[index] }
|
fn index(&self, index: T) -> &Self::Output { &self.deref()[index] }
|
||||||
}
|
}
|
||||||
impl Borrow<[Tok<String>]> for VName {
|
impl Borrow<[IStr]> for VName {
|
||||||
fn borrow(&self) -> &[Tok<String>] { self.0.borrow() }
|
fn borrow(&self) -> &[IStr] { self.0.borrow() }
|
||||||
}
|
}
|
||||||
impl Deref for VName {
|
impl Deref for VName {
|
||||||
type Target = [Tok<String>];
|
type Target = [IStr];
|
||||||
fn deref(&self) -> &Self::Target { self.borrow() }
|
fn deref(&self) -> &Self::Target { self.borrow() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,11 +177,9 @@ impl Deref for VName {
|
|||||||
/// empty sequence
|
/// empty sequence
|
||||||
#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct EmptyNameError;
|
pub struct EmptyNameError;
|
||||||
impl TryFrom<&[Tok<String>]> for VName {
|
impl TryFrom<&[IStr]> for VName {
|
||||||
type Error = EmptyNameError;
|
type Error = EmptyNameError;
|
||||||
fn try_from(value: &[Tok<String>]) -> Result<Self, Self::Error> {
|
fn try_from(value: &[IStr]) -> Result<Self, Self::Error> { Self::new(value.iter().cloned()) }
|
||||||
Self::new(value.iter().cloned())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An interned representation of a namespaced identifier.
|
/// An interned representation of a namespaced identifier.
|
||||||
@@ -206,37 +188,38 @@ impl TryFrom<&[Tok<String>]> for VName {
|
|||||||
///
|
///
|
||||||
/// See also [VName]
|
/// See also [VName]
|
||||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||||
pub struct Sym(Tok<Vec<Tok<String>>>);
|
pub struct Sym(IStrv);
|
||||||
impl Sym {
|
impl Sym {
|
||||||
/// Assert that the sequence isn't empty, intern it and wrap it in a [Sym] to
|
/// Assert that the sequence isn't empty, intern it and wrap it in a [Sym] to
|
||||||
/// represent this invariant
|
/// represent this invariant
|
||||||
pub async fn new(
|
pub async fn new(v: impl IntoIterator<Item = IStr>) -> Result<Self, EmptyNameError> {
|
||||||
v: impl IntoIterator<Item = Tok<String>>,
|
|
||||||
i: &Interner,
|
|
||||||
) -> Result<Self, EmptyNameError> {
|
|
||||||
let items = v.into_iter().collect_vec();
|
let items = v.into_iter().collect_vec();
|
||||||
Self::from_tok(i.i(&items).await)
|
Self::from_tok(iv(&items).await)
|
||||||
}
|
}
|
||||||
/// Read a `::` separated namespaced name.
|
/// Read a `::` separated namespaced name. Do not use this for statically
|
||||||
pub async fn parse(s: &str, i: &Interner) -> Result<Self, EmptyNameError> {
|
/// known names, use the [sym] macro instead which is cached.
|
||||||
Ok(Sym(i.i(&VName::parse(s, i).await?.into_vec()).await))
|
pub async fn parse(s: &str) -> Result<Self, EmptyNameError> {
|
||||||
|
Ok(Sym(iv(&VName::parse(s).await?.into_vec()).await))
|
||||||
}
|
}
|
||||||
/// Assert that a token isn't empty, and wrap it in a [Sym]
|
/// Assert that a token isn't empty, and wrap it in a [Sym]
|
||||||
pub fn from_tok(t: Tok<Vec<Tok<String>>>) -> Result<Self, EmptyNameError> {
|
pub fn from_tok(t: IStrv) -> Result<Self, EmptyNameError> {
|
||||||
if t.is_empty() { Err(EmptyNameError) } else { Ok(Self(t)) }
|
if t.is_empty() { Err(EmptyNameError) } else { Ok(Self(t)) }
|
||||||
}
|
}
|
||||||
/// Grab the interner token
|
/// Grab the interner token
|
||||||
pub fn tok(&self) -> Tok<Vec<Tok<String>>> { self.0.clone() }
|
pub fn tok(&self) -> IStrv { self.0.clone() }
|
||||||
/// Get a number unique to this name suitable for arbitrary ordering.
|
/// Get a number unique to this name suitable for arbitrary ordering.
|
||||||
pub fn id(&self) -> NonZeroU64 { self.0.to_api().get_id() }
|
pub fn id(&self) -> NonZeroU64 { self.0.to_api().0 }
|
||||||
/// Extern the sym for editing
|
/// Extern the sym for editing
|
||||||
pub fn to_vname(&self) -> VName { VName(self[..].to_vec()) }
|
pub fn to_vname(&self) -> VName { VName(self[..].to_vec()) }
|
||||||
pub async fn from_api(marker: api::TStrv, i: &Interner) -> Sym {
|
/// Decode from a message
|
||||||
Self::from_tok(Tok::from_api(marker, i).await).expect("Empty sequence found for serialized Sym")
|
pub async fn from_api(marker: api::TStrv) -> Sym {
|
||||||
|
Self::from_tok(ev(marker).await).expect("Empty sequence found for serialized Sym")
|
||||||
}
|
}
|
||||||
|
/// Encode into a message
|
||||||
pub fn to_api(&self) -> api::TStrv { self.tok().to_api() }
|
pub fn to_api(&self) -> api::TStrv { self.tok().to_api() }
|
||||||
pub async fn suffix(&self, tokv: impl IntoIterator<Item = Tok<String>>, i: &Interner) -> Sym {
|
/// Copy the symbol and extend it with a suffix
|
||||||
Self::new(self.0.iter().cloned().chain(tokv), i).await.unwrap()
|
pub async fn suffix(&self, tokv: impl IntoIterator<Item = IStr>) -> Sym {
|
||||||
|
Self::new(self.0.iter().cloned().chain(tokv)).await.unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl fmt::Debug for Sym {
|
impl fmt::Debug for Sym {
|
||||||
@@ -248,34 +231,32 @@ impl fmt::Display for Sym {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<T> Index<T> for Sym
|
impl<T> Index<T> for Sym
|
||||||
where [Tok<String>]: Index<T>
|
where [IStr]: Index<T>
|
||||||
{
|
{
|
||||||
type Output = <[Tok<String>] as Index<T>>::Output;
|
type Output = <[IStr] as Index<T>>::Output;
|
||||||
|
|
||||||
fn index(&self, index: T) -> &Self::Output { &self.deref()[index] }
|
fn index(&self, index: T) -> &Self::Output { &self.deref()[index] }
|
||||||
}
|
}
|
||||||
impl Borrow<[Tok<String>]> for Sym {
|
impl Borrow<[IStr]> for Sym {
|
||||||
fn borrow(&self) -> &[Tok<String>] { &self.0[..] }
|
fn borrow(&self) -> &[IStr] { &self.0[..] }
|
||||||
}
|
}
|
||||||
impl Deref for Sym {
|
impl Deref for Sym {
|
||||||
type Target = [Tok<String>];
|
type Target = [IStr];
|
||||||
fn deref(&self) -> &Self::Target { self.borrow() }
|
fn deref(&self) -> &Self::Target { self.borrow() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An abstraction over tokenized vs non-tokenized names so that they can be
|
/// An abstraction over tokenized vs non-tokenized names so that they can be
|
||||||
/// handled together in datastructures. The names can never be empty
|
/// handled together in datastructures. The names can never be empty
|
||||||
#[allow(clippy::len_without_is_empty)] // never empty
|
#[allow(clippy::len_without_is_empty, reason = "never empty")]
|
||||||
pub trait NameLike:
|
pub trait NameLike:
|
||||||
'static + Clone + Eq + Hash + fmt::Debug + fmt::Display + Borrow<[Tok<String>]>
|
'static + Clone + Eq + Hash + fmt::Debug + fmt::Display + Borrow<[IStr]>
|
||||||
{
|
{
|
||||||
/// Convert into held slice
|
/// Convert into held slice
|
||||||
fn as_slice(&self) -> &[Tok<String>] { Borrow::<[Tok<String>]>::borrow(self) }
|
fn as_slice(&self) -> &[IStr] { Borrow::<[IStr]>::borrow(self) }
|
||||||
/// Get iterator over tokens
|
/// Get iterator over tokens
|
||||||
fn segs(&self) -> impl NameIter + '_ { self.as_slice().iter().cloned() }
|
fn segs(&self) -> impl NameIter + '_ { self.as_slice().iter().cloned() }
|
||||||
/// Get iterator over string segments
|
/// Get iterator over string segments
|
||||||
fn str_iter(&self) -> impl Iterator<Item = &'_ str> + '_ {
|
fn str_iter(&self) -> impl Iterator<Item = &'_ str> + '_ { self.as_slice().iter().map(|t| &**t) }
|
||||||
self.as_slice().iter().map(|t| t.as_str())
|
|
||||||
}
|
|
||||||
/// Fully resolve the name for printing
|
/// Fully resolve the name for printing
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn to_strv(&self) -> Vec<String> { self.segs().map(|s| s.to_string()).collect() }
|
fn to_strv(&self) -> Vec<String> { self.segs().map(|s| s.to_string()).collect() }
|
||||||
@@ -286,19 +267,19 @@ pub trait NameLike:
|
|||||||
NonZeroUsize::try_from(self.segs().count()).expect("NameLike never empty")
|
NonZeroUsize::try_from(self.segs().count()).expect("NameLike never empty")
|
||||||
}
|
}
|
||||||
/// Like slice's `split_first` except we know that it always returns Some
|
/// Like slice's `split_first` except we know that it always returns Some
|
||||||
fn split_first_seg(&self) -> (Tok<String>, &[Tok<String>]) {
|
fn split_first_seg(&self) -> (IStr, &[IStr]) {
|
||||||
let (foot, torso) = self.as_slice().split_last().expect("NameLike never empty");
|
let (foot, torso) = self.as_slice().split_last().expect("NameLike never empty");
|
||||||
(foot.clone(), torso)
|
(foot.clone(), torso)
|
||||||
}
|
}
|
||||||
/// Like slice's `split_last` except we know that it always returns Some
|
/// Like slice's `split_last` except we know that it always returns Some
|
||||||
fn split_last_seg(&self) -> (Tok<String>, &[Tok<String>]) {
|
fn split_last_seg(&self) -> (IStr, &[IStr]) {
|
||||||
let (foot, torso) = self.as_slice().split_last().expect("NameLike never empty");
|
let (foot, torso) = self.as_slice().split_last().expect("NameLike never empty");
|
||||||
(foot.clone(), torso)
|
(foot.clone(), torso)
|
||||||
}
|
}
|
||||||
/// Get the first element
|
/// Get the first element
|
||||||
fn first_seg(&self) -> Tok<String> { self.split_first_seg().0 }
|
fn first_seg(&self) -> IStr { self.split_first_seg().0 }
|
||||||
/// Get the last element
|
/// Get the last element
|
||||||
fn last_seg(&self) -> Tok<String> { self.split_last_seg().0 }
|
fn last_seg(&self) -> IStr { self.split_last_seg().0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NameLike for Sym {}
|
impl NameLike for Sym {}
|
||||||
@@ -306,19 +287,27 @@ impl NameLike for VName {}
|
|||||||
|
|
||||||
/// Create a [Sym] literal.
|
/// Create a [Sym] literal.
|
||||||
///
|
///
|
||||||
/// Both the name and its components will be cached in a thread-local static so
|
/// The name and its components will be cached in a thread-local static so
|
||||||
/// that subsequent executions of the expression only incur an Arc-clone for
|
/// that subsequent executions of the expression only incur an Arc-clone for
|
||||||
/// cloning the token.
|
/// cloning the token.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! sym {
|
macro_rules! sym {
|
||||||
($seg1:tt $( :: $seg:tt)* ; $i:expr) => {
|
($seg1:tt $( :: $seg:tt)*) => {
|
||||||
$crate::name::Sym::from_tok(
|
$crate::tl_cache!(async $crate::Sym : {
|
||||||
$i.i(&[
|
$crate::Sym::from_tok(
|
||||||
$i.i(stringify!($seg1)).await
|
$crate::iv(&[
|
||||||
$( , $i.i(stringify!($seg)).await )*
|
$crate::is($crate::sym!(@SEG $seg1)).await
|
||||||
|
$( , $crate::is($crate::sym!(@SEG $seg)).await )*
|
||||||
])
|
])
|
||||||
.await
|
.await
|
||||||
).unwrap()
|
).unwrap()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
(@SEG [ $($data:tt)* ]) => {
|
||||||
|
stringify!($($data)*)
|
||||||
|
};
|
||||||
|
(@SEG $data:tt) => {
|
||||||
|
stringify!($data)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,11 +316,13 @@ macro_rules! sym {
|
|||||||
/// The components are interned much like in [sym].
|
/// The components are interned much like in [sym].
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! vname {
|
macro_rules! vname {
|
||||||
($seg1:tt $( :: $seg:tt)* ; $i:expr) => {
|
($seg1:tt $( :: $seg:tt)*) => {
|
||||||
$crate::name::VName::new([
|
$crate::tl_cache!(async $crate::VName : {
|
||||||
$i.i(stringify!($seg1)).await
|
$crate::VName::new([
|
||||||
$( , $i.i(stringify!($seg)).await )*
|
$crate::is(stringify!($seg1)).await
|
||||||
|
$( , $crate::is(stringify!($seg)).await )*
|
||||||
]).unwrap()
|
]).unwrap()
|
||||||
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,14 +331,16 @@ macro_rules! vname {
|
|||||||
/// The components are interned much like in [sym].
|
/// The components are interned much like in [sym].
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! vpath {
|
macro_rules! vpath {
|
||||||
($seg1:tt $( :: $seg:tt)+ ; $i:expr) => {
|
($seg1:tt $( :: $seg:tt)*) => {
|
||||||
$crate::name::VPath(vec![
|
$crate::tl_cache!(async $crate::VPath : {
|
||||||
$i.i(stringify!($seg1)).await
|
$crate::VPath::new(vec![
|
||||||
$( , $i.i(stringify!($seg)).await )+
|
$crate::is(stringify!($seg1)).await
|
||||||
|
$( , $crate::is(stringify!($seg)).await )*
|
||||||
])
|
])
|
||||||
|
})
|
||||||
};
|
};
|
||||||
() => {
|
() => {
|
||||||
$crate::name::VPath(vec![])
|
$crate::VPath::new(vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,39 +348,41 @@ macro_rules! vpath {
|
|||||||
mod test {
|
mod test {
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
use test_executors::spin_on;
|
use orchid_api_traits::spin_on;
|
||||||
|
|
||||||
use super::{NameLike, Sym, VName};
|
use crate::local_interner::local_interner;
|
||||||
use crate::interner::{Interner, Tok};
|
use crate::{IStr, NameLike, Sym, VName, VPath, is, with_interner};
|
||||||
use crate::name::VPath;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn recur() {
|
pub fn recur() {
|
||||||
spin_on(async {
|
spin_on(with_interner(local_interner(), async {
|
||||||
let i = Interner::new_master();
|
let myname = vname!(foo::bar);
|
||||||
let myname = vname!(foo::bar; i);
|
let _borrowed_slice: &[IStr] = myname.borrow();
|
||||||
let _borrowed_slice: &[Tok<String>] = myname.borrow();
|
let _deref_pathslice: &[IStr] = &myname;
|
||||||
let _deref_pathslice: &[Tok<String>] = &myname;
|
let _as_slice_out: &[IStr] = myname.as_slice();
|
||||||
let _as_slice_out: &[Tok<String>] = myname.as_slice();
|
}))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests that literals are correctly interned as equal
|
||||||
#[test]
|
#[test]
|
||||||
fn literals() {
|
pub fn literals() {
|
||||||
spin_on(async {
|
spin_on(with_interner(local_interner(), async {
|
||||||
let i = Interner::new_master();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
sym!(foo::bar::baz; i),
|
sym!(foo::bar::baz),
|
||||||
Sym::new([i.i("foo").await, i.i("bar").await, i.i("baz").await], &i).await.unwrap()
|
Sym::new([is("foo").await, is("bar").await, is("baz").await]).await.unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vname!(foo::bar::baz; i),
|
sym!(foo::bar::[|>]),
|
||||||
VName::new([i.i("foo").await, i.i("bar").await, i.i("baz").await]).unwrap()
|
Sym::new([is("foo").await, is("bar").await, is("|>").await]).await.unwrap()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vpath!(foo::bar::baz; i),
|
vname!(foo::bar::baz),
|
||||||
VPath::new([i.i("foo").await, i.i("bar").await, i.i("baz").await])
|
VName::new([is("foo").await, is("bar").await, is("baz").await]).unwrap()
|
||||||
);
|
);
|
||||||
})
|
assert_eq!(
|
||||||
|
{ vpath!(foo::bar::baz) },
|
||||||
|
VPath::new([is("foo").await, is("bar").await, is("baz").await])
|
||||||
|
);
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,283 +0,0 @@
|
|||||||
//! The NORT (Normal Order Referencing Tree) is the interpreter's runtime
|
|
||||||
//! representation of Orchid programs.
|
|
||||||
//!
|
|
||||||
//! It uses a locator tree to find bound variables in lambda functions, which
|
|
||||||
//! necessitates a normal reduction order because modifying the body by reducing
|
|
||||||
//! expressions would invalidate any locators in enclosing lambdas.
|
|
||||||
//!
|
|
||||||
//! Clauses are held in a mutable `Arc<Mutex<_>>`, so that after substitution
|
|
||||||
//! the instances of the argument remain linked and a reduction step applied to
|
|
||||||
//! any instance transforms all of them.
|
|
||||||
//!
|
|
||||||
//! To improve locality and make the tree less deep and locators shorter,
|
|
||||||
//! function calls store multiple arguments in a deque.
|
|
||||||
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::fmt;
|
|
||||||
use std::ops::DerefMut;
|
|
||||||
use std::sync::{Arc, Mutex, MutexGuard, TryLockError};
|
|
||||||
|
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
use super::path_set::PathSet;
|
|
||||||
use crate::foreign::atom::Atom;
|
|
||||||
#[allow(unused)] // for doc
|
|
||||||
use crate::foreign::atom::Atomic;
|
|
||||||
use crate::foreign::error::{RTErrorObj, RTResult};
|
|
||||||
use crate::foreign::try_from_expr::TryFromExpr;
|
|
||||||
use crate::location::CodeLocation;
|
|
||||||
use crate::name::Sym;
|
|
||||||
#[allow(unused)] // for doc
|
|
||||||
use crate::parse::parsed;
|
|
||||||
use crate::utils::ddispatch::request;
|
|
||||||
|
|
||||||
/// Kinda like [AsMut] except it supports a guard
|
|
||||||
pub(crate) trait AsDerefMut<T> {
|
|
||||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = T> + '_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An expression with metadata
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Expr {
|
|
||||||
/// The actual value
|
|
||||||
pub clause: ClauseInst,
|
|
||||||
/// Information about the code that produced this value
|
|
||||||
pub location: CodeLocation,
|
|
||||||
}
|
|
||||||
impl Expr {
|
|
||||||
/// Constructor
|
|
||||||
pub fn new(clause: ClauseInst, location: CodeLocation) -> Self { Self { clause, location } }
|
|
||||||
/// Obtain the location of the expression
|
|
||||||
pub fn location(&self) -> CodeLocation { self.location.clone() }
|
|
||||||
|
|
||||||
/// Convert into any type that implements [TryFromExpr]. Calls to this
|
|
||||||
/// function are generated wherever a conversion is elided in an extern
|
|
||||||
/// function.
|
|
||||||
pub fn downcast<T: TryFromExpr>(self) -> RTResult<T> {
|
|
||||||
let Expr { mut clause, location } = self;
|
|
||||||
loop {
|
|
||||||
let cls_deref = clause.cls_mut();
|
|
||||||
match &*cls_deref {
|
|
||||||
Clause::Identity(alt) => {
|
|
||||||
let temp = alt.clone();
|
|
||||||
drop(cls_deref);
|
|
||||||
clause = temp;
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
drop(cls_deref);
|
|
||||||
return T::from_expr(Expr { clause, location });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Visit all expressions in the tree. The search can be exited early by
|
|
||||||
/// returning [Some]
|
|
||||||
///
|
|
||||||
/// See also [parsed::Expr::search_all]
|
|
||||||
pub fn search_all<T>(&self, predicate: &mut impl FnMut(&Self) -> Option<T>) -> Option<T> {
|
|
||||||
if let Some(t) = predicate(self) {
|
|
||||||
return Some(t);
|
|
||||||
}
|
|
||||||
self.clause.inspect(|c| match c {
|
|
||||||
Clause::Identity(_alt) => unreachable!("Handled by inspect"),
|
|
||||||
Clause::Apply { f, x } =>
|
|
||||||
(f.search_all(predicate)).or_else(|| x.iter().find_map(|x| x.search_all(predicate))),
|
|
||||||
Clause::Lambda { body, .. } => body.search_all(predicate),
|
|
||||||
Clause::Constant(_) | Clause::LambdaArg | Clause::Atom(_) | Clause::Bottom(_) => None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clone the refcounted [ClauseInst] out of the expression
|
|
||||||
#[must_use]
|
|
||||||
pub fn clsi(&self) -> ClauseInst { self.clause.clone() }
|
|
||||||
|
|
||||||
/// Read-Write access to the [Clause]
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// if the clause is already borrowed
|
|
||||||
pub fn cls_mut(&self) -> MutexGuard<'_, Clause> { self.clause.cls_mut() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Expr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{:?}@{}", self.clause, self.location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Expr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.clause) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsDerefMut<Clause> for Expr {
|
|
||||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ { self.clause.cls_mut() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper around expressions to handle their multiple occurences in
|
|
||||||
/// the tree together
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ClauseInst(pub Arc<Mutex<Clause>>);
|
|
||||||
impl ClauseInst {
|
|
||||||
/// Wrap a [Clause] in a shared container so that normalization steps are
|
|
||||||
/// applied to all references
|
|
||||||
#[must_use]
|
|
||||||
pub fn new(cls: Clause) -> Self { Self(Arc::new(Mutex::new(cls))) }
|
|
||||||
|
|
||||||
/// Take the [Clause] out of this container if it's the last reference to it,
|
|
||||||
/// or return self.
|
|
||||||
pub fn try_unwrap(self) -> Result<Clause, ClauseInst> {
|
|
||||||
Arc::try_unwrap(self.0).map(|c| c.into_inner().unwrap()).map_err(Self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Read-Write access to the shared clause instance
|
|
||||||
///
|
|
||||||
/// if the clause is already borrowed, this will block until it is released.
|
|
||||||
pub fn cls_mut(&self) -> MutexGuard<'_, Clause> { self.0.lock().unwrap() }
|
|
||||||
|
|
||||||
/// Call a predicate on the clause, returning whatever the
|
|
||||||
/// predicate returns. This is a convenience function for reaching
|
|
||||||
/// through the [Mutex]. The clause will never be [Clause::Identity].
|
|
||||||
#[must_use]
|
|
||||||
pub fn inspect<T>(&self, predicate: impl FnOnce(&Clause) -> T) -> T {
|
|
||||||
match &*self.cls_mut() {
|
|
||||||
Clause::Identity(sub) => sub.inspect(predicate),
|
|
||||||
x => predicate(x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If this expression is an [Atomic], request an object of the given type.
|
|
||||||
/// If it's not an atomic, fail the request automatically.
|
|
||||||
#[must_use = "your request might not have succeeded"]
|
|
||||||
pub fn request<T: 'static>(&self) -> Option<T> {
|
|
||||||
match &*self.cls_mut() {
|
|
||||||
Clause::Atom(a) => request(&*a.0),
|
|
||||||
Clause::Identity(alt) => alt.request(),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Associate a location with this clause
|
|
||||||
pub fn into_expr(self, location: CodeLocation) -> Expr {
|
|
||||||
Expr { clause: self.clone(), location: location.clone() }
|
|
||||||
}
|
|
||||||
/// Check ahead-of-time if this clause contains an atom. Calls
|
|
||||||
/// [ClauseInst#cls] for read access.
|
|
||||||
///
|
|
||||||
/// Since atoms cannot become normalizable, if this is true and previous
|
|
||||||
/// normalization failed, the atom is known to be in normal form.
|
|
||||||
pub fn is_atom(&self) -> bool { matches!(&*self.cls_mut(), Clause::Atom(_)) }
|
|
||||||
|
|
||||||
/// Tries to unwrap the [Arc]. If that fails, clones it field by field.
|
|
||||||
/// If it's a [Clause::Atom] which cannot be cloned, wraps it in a
|
|
||||||
/// [Clause::Identity].
|
|
||||||
///
|
|
||||||
/// Implementation of [crate::foreign::to_clause::ToClause::to_clause]. The
|
|
||||||
/// trait is more general so it requires a location which this one doesn't.
|
|
||||||
pub fn into_cls(self) -> Clause {
|
|
||||||
self.try_unwrap().unwrap_or_else(|clsi| match &*clsi.cls_mut() {
|
|
||||||
Clause::Apply { f, x } => Clause::Apply { f: f.clone(), x: x.clone() },
|
|
||||||
Clause::Atom(_) => Clause::Identity(clsi.clone()),
|
|
||||||
Clause::Bottom(e) => Clause::Bottom(e.clone()),
|
|
||||||
Clause::Constant(c) => Clause::Constant(c.clone()),
|
|
||||||
Clause::Identity(sub) => Clause::Identity(sub.clone()),
|
|
||||||
Clause::Lambda { args, body } => Clause::Lambda { args: args.clone(), body: body.clone() },
|
|
||||||
Clause::LambdaArg => Clause::LambdaArg,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decides if this clause is the exact same instance as another. Most useful
|
|
||||||
/// to detect potential deadlocks.
|
|
||||||
pub fn is_same(&self, other: &Self) -> bool { Arc::ptr_eq(&self.0, &other.0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for ClauseInst {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self.0.try_lock() {
|
|
||||||
Ok(expr) => write!(f, "{expr:?}"),
|
|
||||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
|
||||||
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ClauseInst {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self.0.try_lock() {
|
|
||||||
Ok(expr) => write!(f, "{expr}"),
|
|
||||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
|
||||||
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsDerefMut<Clause> for ClauseInst {
|
|
||||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ { self.cls_mut() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Distinct types of expressions recognized by the interpreter
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Clause {
|
|
||||||
/// An expression that causes an error
|
|
||||||
Bottom(RTErrorObj),
|
|
||||||
/// Indicates that this [ClauseInst] has the same value as the other
|
|
||||||
/// [ClauseInst]. This has two benefits;
|
|
||||||
///
|
|
||||||
/// - [Clause] and therefore [Atomic] doesn't have to be [Clone] which saves
|
|
||||||
/// many synchronization primitives and reference counters in usercode
|
|
||||||
/// - it enforces on the type level that all copies are normalized together,
|
|
||||||
/// so accidental inefficiency in the interpreter is rarer.
|
|
||||||
///
|
|
||||||
/// That being said, it's still arbitrary many indirections, so when possible
|
|
||||||
/// APIs should be usable with a [ClauseInst] directly.
|
|
||||||
Identity(ClauseInst),
|
|
||||||
/// An opaque non-callable value, eg. a file handle
|
|
||||||
Atom(Atom),
|
|
||||||
/// A function application
|
|
||||||
Apply {
|
|
||||||
/// Function to be applied
|
|
||||||
f: Expr,
|
|
||||||
/// Argument to be substituted in the function
|
|
||||||
x: VecDeque<Expr>,
|
|
||||||
},
|
|
||||||
/// A name to be looked up in the interpreter's symbol table
|
|
||||||
Constant(Sym),
|
|
||||||
/// A function
|
|
||||||
Lambda {
|
|
||||||
/// A collection of (zero or more) paths to placeholders belonging to this
|
|
||||||
/// function
|
|
||||||
args: Option<PathSet>,
|
|
||||||
/// The tree produced by this function, with placeholders where the
|
|
||||||
/// argument will go
|
|
||||||
body: Expr,
|
|
||||||
},
|
|
||||||
/// A placeholder within a function that will be replaced upon application
|
|
||||||
LambdaArg,
|
|
||||||
}
|
|
||||||
impl Clause {
|
|
||||||
/// Wrap a clause in a refcounted lock
|
|
||||||
pub fn into_inst(self) -> ClauseInst { ClauseInst::new(self) }
|
|
||||||
/// Wrap a clause in an expression.
|
|
||||||
pub fn into_expr(self, location: CodeLocation) -> Expr { self.into_inst().into_expr(location) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Clause {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Clause::Atom(a) => write!(f, "{a:?}"),
|
|
||||||
Clause::Bottom(err) => write!(f, "bottom({err})"),
|
|
||||||
Clause::LambdaArg => write!(f, "arg"),
|
|
||||||
Clause::Apply { f: fun, x } => write!(f, "({fun} {})", x.iter().join(" ")),
|
|
||||||
Clause::Lambda { args, body } => match args {
|
|
||||||
Some(path) => write!(f, "[\\{path}.{body}]"),
|
|
||||||
None => write!(f, "[\\_.{body}]"),
|
|
||||||
},
|
|
||||||
Clause::Constant(t) => write!(f, "{t}"),
|
|
||||||
Clause::Identity(other) => write!(f, "{{{other}}}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsDerefMut<Clause> for Clause {
|
|
||||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ { self }
|
|
||||||
}
|
|
||||||
@@ -3,10 +3,7 @@ use std::ops::Range;
|
|||||||
|
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
use crate::error::{OrcErrv, mk_errv};
|
use crate::{OrcErrv, SrcRange, Sym, is, mk_errv};
|
||||||
use crate::interner::Interner;
|
|
||||||
use crate::location::SrcRange;
|
|
||||||
use crate::name::Sym;
|
|
||||||
|
|
||||||
/// A number, either floating point or unsigned int, parsed by Orchid.
|
/// A number, either floating point or unsigned int, parsed by Orchid.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
@@ -55,14 +52,9 @@ pub struct NumError {
|
|||||||
pub kind: NumErrorKind,
|
pub kind: NumErrorKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn num_to_errv(
|
pub async fn num_to_errv(NumError { kind, range }: NumError, offset: u32, source: &Sym) -> OrcErrv {
|
||||||
NumError { kind, range }: NumError,
|
|
||||||
offset: u32,
|
|
||||||
source: &Sym,
|
|
||||||
i: &Interner,
|
|
||||||
) -> OrcErrv {
|
|
||||||
mk_errv(
|
mk_errv(
|
||||||
i.i("Failed to parse number").await,
|
is("Failed to parse number").await,
|
||||||
match kind {
|
match kind {
|
||||||
NumErrorKind::NaN => "NaN emerged during parsing",
|
NumErrorKind::NaN => "NaN emerged during parsing",
|
||||||
NumErrorKind::InvalidDigit => "non-digit character encountered",
|
NumErrorKind::InvalidDigit => "non-digit character encountered",
|
||||||
|
|||||||
@@ -1,261 +0,0 @@
|
|||||||
//! Adaptor trait to embed Rust values in Orchid expressions
|
|
||||||
|
|
||||||
use std::any::Any;
|
|
||||||
use std::fmt;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use never::Never;
|
|
||||||
|
|
||||||
use super::error::{RTError, RTResult};
|
|
||||||
use crate::interpreter::context::{RunEnv, RunParams};
|
|
||||||
use crate::interpreter::nort;
|
|
||||||
use crate::location::{CodeLocation, SourceRange};
|
|
||||||
use crate::name::NameLike;
|
|
||||||
use crate::parse::lexer::Lexeme;
|
|
||||||
use crate::parse::parsed;
|
|
||||||
use crate::utils::ddispatch::{request, Request, Responder};
|
|
||||||
|
|
||||||
/// Information returned by [Atomic::run].
|
|
||||||
pub enum AtomicReturn {
|
|
||||||
/// No work was done. If the atom takes an argument, it can be provided now
|
|
||||||
Inert(Atom),
|
|
||||||
/// Work was done, returns new clause and consumed gas. 1 gas is already
|
|
||||||
/// consumed by the virtual call, so nonzero values indicate expensive
|
|
||||||
/// operations.
|
|
||||||
Change(usize, nort::Clause),
|
|
||||||
}
|
|
||||||
impl AtomicReturn {
|
|
||||||
/// Report indicating that the value is inert. The result here is always [Ok],
|
|
||||||
/// it's meant to match the return type of [Atomic::run]
|
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
|
||||||
pub fn inert<T: Atomic, E>(this: T) -> Result<Self, E> { Ok(Self::Inert(Atom::new(this))) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returned by [Atomic::run]
|
|
||||||
pub type AtomicResult = RTResult<AtomicReturn>;
|
|
||||||
|
|
||||||
/// General error produced when a non-function [Atom] is applied to something as
|
|
||||||
/// a function.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct NotAFunction(pub nort::Expr);
|
|
||||||
impl RTError for NotAFunction {}
|
|
||||||
impl fmt::Display for NotAFunction {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "{:?} is not a function", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Information about a function call presented to an external function
|
|
||||||
pub struct CallData<'a, 'b> {
|
|
||||||
/// Location of the function expression
|
|
||||||
pub location: CodeLocation,
|
|
||||||
/// The argument the function was called on. Functions are curried
|
|
||||||
pub arg: nort::Expr,
|
|
||||||
/// Globally available information such as the list of all constants
|
|
||||||
pub env: &'a RunEnv<'b>,
|
|
||||||
/// Resource limits and other details specific to this interpreter run
|
|
||||||
pub params: &'a mut RunParams,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Information about a normalization run presented to an atom
|
|
||||||
pub struct RunData<'a, 'b> {
|
|
||||||
/// Location of the atom
|
|
||||||
pub location: CodeLocation,
|
|
||||||
/// Globally available information such as the list of all constants
|
|
||||||
pub env: &'a RunEnv<'b>,
|
|
||||||
/// Resource limits and other details specific to this interpreter run
|
|
||||||
pub params: &'a mut RunParams,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Functionality the interpreter needs to handle a value
|
|
||||||
///
|
|
||||||
/// # Lifecycle methods
|
|
||||||
///
|
|
||||||
/// Atomics expose the methods [Atomic::redirect], [Atomic::run],
|
|
||||||
/// [Atomic::apply] and [Atomic::apply_mut] to interact with the interpreter.
|
|
||||||
/// The interpreter first tries to call `redirect` to find a subexpression to
|
|
||||||
/// normalize. If it returns `None` or the subexpression is inert, `run` is
|
|
||||||
/// called. `run` takes ownership of the value and returns a new one.
|
|
||||||
///
|
|
||||||
/// If `run` indicated in its return value that the result is inert and the atom
|
|
||||||
/// is in the position of a function, `apply` or `apply_mut` is called depending
|
|
||||||
/// upon whether the atom is referenced elsewhere. `apply` falls back to
|
|
||||||
/// `apply_mut` so implementing it is considered an optimization to avoid
|
|
||||||
/// excessive copying.
|
|
||||||
///
|
|
||||||
/// Atoms don't generally have to be copyable because clauses are refcounted in
|
|
||||||
/// the interpreter, but Orchid code is always free to duplicate the references
|
|
||||||
/// and apply them as functions to multiple different arguments so atoms that
|
|
||||||
/// represent functions have to support application by-ref without consuming the
|
|
||||||
/// function itself.
|
|
||||||
pub trait Atomic: Any + fmt::Debug + Responder + Send
|
|
||||||
where Self: 'static
|
|
||||||
{
|
|
||||||
/// Casts this value to [Any] so that its original value can be salvaged
|
|
||||||
/// during introspection by other external code.
|
|
||||||
///
|
|
||||||
/// This function should be implemented in exactly one way:
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
|
|
||||||
/// ```
|
|
||||||
#[must_use]
|
|
||||||
fn as_any(self: Box<Self>) -> Box<dyn Any>;
|
|
||||||
/// See [Atomic::as_any], exactly the same but for references
|
|
||||||
#[must_use]
|
|
||||||
fn as_any_ref(&self) -> &dyn Any;
|
|
||||||
/// Print the atom's type name. Should only ever be implemented as
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// fn type_name(&self) -> &'static str { std::any::type_name::<Self>() }
|
|
||||||
/// ```
|
|
||||||
fn type_name(&self) -> &'static str;
|
|
||||||
|
|
||||||
/// Returns a reference to a possible expression held inside the atom which
|
|
||||||
/// can be reduced. For an overview of the lifecycle see [Atomic]
|
|
||||||
fn redirect(&mut self) -> Option<&mut nort::Expr>;
|
|
||||||
|
|
||||||
/// Attempt to normalize this value. If it wraps a value, this should report
|
|
||||||
/// inert. If it wraps a computation, it should execute one logical step of
|
|
||||||
/// the computation and return a structure representing the next.
|
|
||||||
///
|
|
||||||
/// For an overview of the lifecycle see [Atomic]
|
|
||||||
fn run(self: Box<Self>, run: RunData) -> AtomicResult;
|
|
||||||
|
|
||||||
/// Combine the function with an argument to produce a new clause. Falls back
|
|
||||||
/// to [Atomic::apply_mut] by default.
|
|
||||||
///
|
|
||||||
/// For an overview of the lifecycle see [Atomic]
|
|
||||||
fn apply(mut self: Box<Self>, call: CallData) -> RTResult<nort::Clause> { self.apply_mut(call) }
|
|
||||||
|
|
||||||
/// Combine the function with an argument to produce a new clause
|
|
||||||
///
|
|
||||||
/// For an overview of the lifecycle see [Atomic]
|
|
||||||
fn apply_mut(&mut self, call: CallData) -> RTResult<nort::Clause>;
|
|
||||||
|
|
||||||
/// Must return true for atoms parsed from identical source.
|
|
||||||
/// If the atom cannot be parsed from source, it can safely be ignored
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn parser_eq(&self, other: &dyn Atomic) -> bool { false }
|
|
||||||
|
|
||||||
/// Wrap the atom in a clause to be placed in an [AtomicResult].
|
|
||||||
#[must_use]
|
|
||||||
fn atom_cls(self) -> nort::Clause
|
|
||||||
where Self: Sized {
|
|
||||||
nort::Clause::Atom(Atom(Box::new(self)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand for `self.atom_cls().to_inst()`
|
|
||||||
fn atom_clsi(self) -> nort::ClauseInst
|
|
||||||
where Self: Sized {
|
|
||||||
self.atom_cls().into_inst()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrap the atom in a new expression instance to be placed in a tree
|
|
||||||
#[must_use]
|
|
||||||
fn atom_expr(self, location: CodeLocation) -> nort::Expr
|
|
||||||
where Self: Sized {
|
|
||||||
self.atom_clsi().into_expr(location)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrap the atom in a clause to be placed in a
|
|
||||||
/// [crate::parse::parsed::SourceLine].
|
|
||||||
#[must_use]
|
|
||||||
fn ast_cls(self) -> parsed::Clause
|
|
||||||
where Self: Sized + Clone {
|
|
||||||
parsed::Clause::Atom(AtomGenerator::cloner(self))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrap the atom in an expression to be placed in a
|
|
||||||
/// [crate::parse::parsed::SourceLine].
|
|
||||||
#[must_use]
|
|
||||||
fn ast_exp<N: NameLike>(self, range: SourceRange) -> parsed::Expr
|
|
||||||
where Self: Sized + Clone {
|
|
||||||
self.ast_cls().into_expr(range)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrap this atomic value in a lexeme. This means that the atom will
|
|
||||||
/// participate in macro reproject, so it must implement [Atomic::parser_eq].
|
|
||||||
fn lexeme(self) -> Lexeme
|
|
||||||
where Self: Sized + Clone {
|
|
||||||
Lexeme::Atom(AtomGenerator::cloner(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A struct for generating any number of [Atom]s. Since atoms aren't Clone,
|
|
||||||
/// this represents the ability to create any number of instances of an atom
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct AtomGenerator(Arc<dyn Fn() -> Atom + Send + Sync>);
|
|
||||||
impl AtomGenerator {
|
|
||||||
/// Use a factory function to create any number of atoms
|
|
||||||
pub fn new(f: impl Fn() -> Atom + Send + Sync + 'static) -> Self { Self(Arc::new(f)) }
|
|
||||||
/// Clone a representative atom when called
|
|
||||||
pub fn cloner(atom: impl Atomic + Clone) -> Self {
|
|
||||||
let lock = Mutex::new(atom);
|
|
||||||
Self::new(move || Atom::new(lock.lock().unwrap().clone()))
|
|
||||||
}
|
|
||||||
/// Generate an atom
|
|
||||||
pub fn run(&self) -> Atom { self.0() }
|
|
||||||
}
|
|
||||||
impl fmt::Debug for AtomGenerator {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.run()) }
|
|
||||||
}
|
|
||||||
impl PartialEq for AtomGenerator {
|
|
||||||
fn eq(&self, other: &Self) -> bool { self.run().0.parser_eq(&*other.run().0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a black box unit of data with its own normalization steps.
|
|
||||||
/// Typically Rust functions integrated with [super::fn_bridge::xfn] will
|
|
||||||
/// produce and consume [Atom]s to represent both raw data, pending
|
|
||||||
/// computational tasks, and curried partial calls awaiting their next argument.
|
|
||||||
pub struct Atom(pub Box<dyn Atomic>);
|
|
||||||
impl Atom {
|
|
||||||
/// Wrap an [Atomic] in a type-erased box
|
|
||||||
#[must_use]
|
|
||||||
pub fn new<T: 'static + Atomic>(data: T) -> Self { Self(Box::new(data) as Box<dyn Atomic>) }
|
|
||||||
/// Get the contained data
|
|
||||||
#[must_use]
|
|
||||||
pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic }
|
|
||||||
/// Test the type of the contained data without downcasting
|
|
||||||
#[must_use]
|
|
||||||
pub fn is<T: Atomic>(&self) -> bool { self.data().as_any_ref().is::<T>() }
|
|
||||||
/// Downcast contained data, panic if it isn't the specified type
|
|
||||||
#[must_use]
|
|
||||||
pub fn downcast<T: Atomic>(self) -> T {
|
|
||||||
*self.0.as_any().downcast().expect("Type mismatch on Atom::cast")
|
|
||||||
}
|
|
||||||
/// Normalize the contained data
|
|
||||||
pub fn run(self, run: RunData<'_, '_>) -> AtomicResult { self.0.run(run) }
|
|
||||||
/// Request a delegate from the encapsulated data
|
|
||||||
pub fn request<T: 'static>(&self) -> Option<T> { request(self.0.as_ref()) }
|
|
||||||
/// Downcast the atom to a concrete atomic type, or return the original atom
|
|
||||||
/// if it is not the specified type
|
|
||||||
pub fn try_downcast<T: Atomic>(self) -> Result<T, Self> {
|
|
||||||
match self.0.as_any_ref().is::<T>() {
|
|
||||||
true => Ok(*self.0.as_any().downcast().expect("checked just above")),
|
|
||||||
false => Err(self),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Downcast an atom by reference
|
|
||||||
pub fn downcast_ref<T: Atomic>(&self) -> Option<&T> { self.0.as_any_ref().downcast_ref() }
|
|
||||||
/// Combine the function with an argument to produce a new clause
|
|
||||||
pub fn apply(self, call: CallData) -> RTResult<nort::Clause> { self.0.apply(call) }
|
|
||||||
/// Combine the function with an argument to produce a new clause
|
|
||||||
pub fn apply_mut(&mut self, call: CallData) -> RTResult<nort::Clause> { self.0.apply_mut(call) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Atom {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.data()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Responder for Never {
|
|
||||||
fn respond(&self, _request: Request) { match *self {} }
|
|
||||||
}
|
|
||||||
impl Atomic for Never {
|
|
||||||
fn as_any(self: Box<Self>) -> Box<dyn Any> { match *self {} }
|
|
||||||
fn as_any_ref(&self) -> &dyn Any { match *self {} }
|
|
||||||
fn type_name(&self) -> &'static str { match *self {} }
|
|
||||||
fn redirect(&mut self) -> Option<&mut nort::Expr> { match *self {} }
|
|
||||||
fn run(self: Box<Self>, _: RunData) -> AtomicResult { match *self {} }
|
|
||||||
fn apply_mut(&mut self, _: CallData) -> RTResult<nort::Clause> { match *self {} }
|
|
||||||
}
|
|
||||||
5
orchid-base/src/on_drop.rs
Normal file
5
orchid-base/src/on_drop.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub struct OnDrop<F: FnOnce()>(Option<F>);
|
||||||
|
impl<F: FnOnce()> Drop for OnDrop<F> {
|
||||||
|
fn drop(&mut self) { (self.0.take().unwrap())() }
|
||||||
|
}
|
||||||
|
pub fn on_drop<F: FnOnce()>(f: F) -> OnDrop<F> { OnDrop(Some(f)) }
|
||||||
@@ -6,32 +6,19 @@ use futures::FutureExt;
|
|||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::api;
|
use crate::{
|
||||||
use crate::error::{OrcErrv, OrcRes, Reporter, mk_errv};
|
ExprRepr, ExtraTok, FmtCtx, FmtUnit, Format, IStr, OrcErrv, OrcRes, Paren, SrcRange, Sym,
|
||||||
use crate::format::{FmtCtx, FmtUnit, Format, fmt};
|
TokTree, Token, VName, VPath, api, es, fmt, is, mk_errv, report, ttv_fmt, ttv_range,
|
||||||
use crate::interner::{Interner, Tok};
|
};
|
||||||
use crate::location::SrcRange;
|
|
||||||
use crate::name::{Sym, VName, VPath};
|
|
||||||
use crate::tree::{ExprRepr, ExtraTok, Paren, TokTree, Token, ttv_fmt, ttv_range};
|
|
||||||
|
|
||||||
pub trait ParseCtx {
|
|
||||||
#[must_use]
|
|
||||||
fn i(&self) -> &Interner;
|
|
||||||
#[must_use]
|
|
||||||
fn rep(&self) -> &Reporter;
|
|
||||||
}
|
|
||||||
pub struct ParseCtxImpl<'a> {
|
|
||||||
pub i: &'a Interner,
|
|
||||||
pub r: &'a Reporter,
|
|
||||||
}
|
|
||||||
impl ParseCtx for ParseCtxImpl<'_> {
|
|
||||||
fn i(&self) -> &Interner { self.i }
|
|
||||||
fn rep(&self) -> &Reporter { self.r }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// A character that can appear at the start of a name; `[a-zA-Z_]`
|
||||||
pub fn name_start(c: char) -> bool { c.is_alphabetic() || c == '_' }
|
pub fn name_start(c: char) -> bool { c.is_alphabetic() || c == '_' }
|
||||||
|
/// A character that can appear inside a name after the start `[a-zA-Z0-9_]`
|
||||||
pub fn name_char(c: char) -> bool { name_start(c) || c.is_numeric() }
|
pub fn name_char(c: char) -> bool { name_start(c) || c.is_numeric() }
|
||||||
|
/// A character that can appear in an operator. Anything except
|
||||||
|
/// `a-zA-Z0-9_()[]{}\` or whitespace
|
||||||
pub fn op_char(c: char) -> bool { !name_char(c) && !c.is_whitespace() && !"()[]{}\\".contains(c) }
|
pub fn op_char(c: char) -> bool { !name_char(c) && !c.is_whitespace() && !"()[]{}\\".contains(c) }
|
||||||
|
/// Any whitespace except a line break
|
||||||
pub fn unrep_space(c: char) -> bool { c.is_whitespace() && !"\r\n".contains(c) }
|
pub fn unrep_space(c: char) -> bool { c.is_whitespace() && !"\r\n".contains(c) }
|
||||||
|
|
||||||
/// A cheaply copiable subsection of a document that holds onto context data and
|
/// A cheaply copiable subsection of a document that holds onto context data and
|
||||||
@@ -46,7 +33,10 @@ where
|
|||||||
A: ExprRepr,
|
A: ExprRepr,
|
||||||
X: ExtraTok,
|
X: ExtraTok,
|
||||||
{
|
{
|
||||||
|
/// Create a snippet from a fallback token for position tracking and a range
|
||||||
|
/// of tokens
|
||||||
pub fn new(prev: &'a TokTree<A, X>, cur: &'a [TokTree<A, X>]) -> Self { Self { prev, cur } }
|
pub fn new(prev: &'a TokTree<A, X>, cur: &'a [TokTree<A, X>]) -> Self { Self { prev, cur } }
|
||||||
|
/// Split snippet at index
|
||||||
pub fn split_at(self, pos: u32) -> (Self, Self) {
|
pub fn split_at(self, pos: u32) -> (Self, Self) {
|
||||||
let Self { prev, cur } = self;
|
let Self { prev, cur } = self;
|
||||||
let fst = Self { prev, cur: &cur[..pos as usize] };
|
let fst = Self { prev, cur: &cur[..pos as usize] };
|
||||||
@@ -54,23 +44,35 @@ where
|
|||||||
let snd = Self { prev: new_prev, cur: &self.cur[pos as usize..] };
|
let snd = Self { prev: new_prev, cur: &self.cur[pos as usize..] };
|
||||||
(fst, snd)
|
(fst, snd)
|
||||||
}
|
}
|
||||||
|
/// Find the first index that matches a condition
|
||||||
pub fn find_idx(self, mut f: impl FnMut(&Token<A, X>) -> bool) -> Option<u32> {
|
pub fn find_idx(self, mut f: impl FnMut(&Token<A, X>) -> bool) -> Option<u32> {
|
||||||
self.cur.iter().position(|t| f(&t.tok)).map(|t| t as u32)
|
self.cur.iter().position(|t| f(&t.tok)).map(|t| t as u32)
|
||||||
}
|
}
|
||||||
|
/// Get the n-th token
|
||||||
pub fn get(self, idx: u32) -> Option<&'a TokTree<A, X>> { self.cur.get(idx as usize) }
|
pub fn get(self, idx: u32) -> Option<&'a TokTree<A, X>> { self.cur.get(idx as usize) }
|
||||||
|
/// Count how many tokens long the current sequence is. Parenthesized
|
||||||
|
/// subsequences count as 1
|
||||||
pub fn len(self) -> u32 { self.cur.len() as u32 }
|
pub fn len(self) -> u32 { self.cur.len() as u32 }
|
||||||
|
/// The fallback token that can be used for error reporting if this snippet is
|
||||||
|
/// unexpectedly empty
|
||||||
pub fn prev(self) -> &'a TokTree<A, X> { self.prev }
|
pub fn prev(self) -> &'a TokTree<A, X> { self.prev }
|
||||||
|
/// Create a position that describes all tokens in this snippet
|
||||||
pub fn sr(self) -> SrcRange { ttv_range(self.cur).unwrap_or_else(|| self.prev.sr.clone()) }
|
pub fn sr(self) -> SrcRange { ttv_range(self.cur).unwrap_or_else(|| self.prev.sr.clone()) }
|
||||||
pub fn pop_front(self) -> Option<(&'a TokTree<A, X>, Self)> {
|
/// Split the first token
|
||||||
|
pub fn split_first(self) -> Option<(&'a TokTree<A, X>, Self)> {
|
||||||
self.cur.first().map(|r| (r, self.split_at(1).1))
|
self.cur.first().map(|r| (r, self.split_at(1).1))
|
||||||
}
|
}
|
||||||
pub fn pop_back(self) -> Option<(Self, &'a TokTree<A, X>)> {
|
/// Split the last token
|
||||||
|
pub fn split_last(self) -> Option<(Self, &'a TokTree<A, X>)> {
|
||||||
self.cur.last().map(|r| (self.split_at(self.len() - 1).0, r))
|
self.cur.last().map(|r| (self.split_at(self.len() - 1).0, r))
|
||||||
}
|
}
|
||||||
|
/// Split the snippet at the first token that matches the predicate
|
||||||
pub fn split_once(self, f: impl FnMut(&Token<A, X>) -> bool) -> Option<(Self, Self)> {
|
pub fn split_once(self, f: impl FnMut(&Token<A, X>) -> bool) -> Option<(Self, Self)> {
|
||||||
let idx = self.find_idx(f)?;
|
let idx = self.find_idx(f)?;
|
||||||
Some((self.split_at(idx).0, self.split_at(idx + 1).1))
|
Some((self.split_at(idx).0, self.split_at(idx + 1).1))
|
||||||
}
|
}
|
||||||
|
/// Split the snippet at each occurrence of a delimiter matched by the
|
||||||
|
/// predicate
|
||||||
pub fn split(mut self, mut f: impl FnMut(&Token<A, X>) -> bool) -> impl Iterator<Item = Self> {
|
pub fn split(mut self, mut f: impl FnMut(&Token<A, X>) -> bool) -> impl Iterator<Item = Self> {
|
||||||
iter::from_fn(move || {
|
iter::from_fn(move || {
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
@@ -81,7 +83,10 @@ where
|
|||||||
Some(ret)
|
Some(ret)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pub fn is_empty(self) -> bool { self.len() == 0 }
|
/// Returns true if the snippet contains no tokens. Note that thanks to the
|
||||||
|
/// fallback, an empty snippet still has a queriable position
|
||||||
|
pub fn is_empty(self) -> bool { self.cur.is_empty() }
|
||||||
|
/// Skip tokens that are not meant to be significant inside expressions
|
||||||
pub fn skip_fluff(self) -> Self {
|
pub fn skip_fluff(self) -> Self {
|
||||||
let non_fluff_start = self.find_idx(|t| !matches!(t, Token::BR | Token::Comment(_)));
|
let non_fluff_start = self.find_idx(|t| !matches!(t, Token::BR | Token::Comment(_)));
|
||||||
self.split_at(non_fluff_start.unwrap_or(self.len())).1
|
self.split_at(non_fluff_start.unwrap_or(self.len())).1
|
||||||
@@ -101,24 +106,25 @@ impl<A: ExprRepr, X: ExtraTok> Format for Snippet<'_, A, X> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A comment as parsed from code
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Comment {
|
pub struct Comment {
|
||||||
pub text: Tok<String>,
|
pub text: IStr,
|
||||||
pub sr: SrcRange,
|
pub sr: SrcRange,
|
||||||
}
|
}
|
||||||
impl Comment {
|
impl Comment {
|
||||||
// XXX: which of these four are actually used?
|
// XXX: which of these four are actually used?
|
||||||
pub async fn from_api(c: &api::Comment, src: Sym, i: &Interner) -> Self {
|
pub async fn from_api(c: &api::Comment, src: Sym) -> Self {
|
||||||
Self { text: i.ex(c.text).await, sr: SrcRange::new(c.range.clone(), &src) }
|
Self { text: es(c.text).await, sr: SrcRange::new(c.range.clone(), &src) }
|
||||||
}
|
}
|
||||||
pub async fn from_tk(tk: &TokTree<impl ExprRepr, impl ExtraTok>, i: &Interner) -> Option<Self> {
|
pub async fn from_tk(tk: &TokTree<impl ExprRepr, impl ExtraTok>) -> Option<Self> {
|
||||||
match &tk.tok {
|
match &tk.tok {
|
||||||
Token::Comment(text) => Some(Self { text: i.i(&**text).await, sr: tk.sr.clone() }),
|
Token::Comment(text) => Some(Self { text: text.clone(), sr: tk.sr.clone() }),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn to_tk<R: ExprRepr, X: ExtraTok>(&self) -> TokTree<R, X> {
|
pub fn to_tk<R: ExprRepr, X: ExtraTok>(&self) -> TokTree<R, X> {
|
||||||
TokTree { tok: Token::Comment(self.text.rc().clone()), sr: self.sr.clone() }
|
TokTree { tok: Token::Comment(self.text.clone()), sr: self.sr.clone() }
|
||||||
}
|
}
|
||||||
pub fn to_api(&self) -> api::Comment {
|
pub fn to_api(&self) -> api::Comment {
|
||||||
api::Comment { range: self.sr.range(), text: self.text.to_api() }
|
api::Comment { range: self.sr.range(), text: self.text.to_api() }
|
||||||
@@ -129,8 +135,16 @@ impl fmt::Display for Comment {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "--[{}]--", self.text) }
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "--[{}]--", self.text) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Split a snippet into items by line breaks outside parentheses, unwrap lines
|
||||||
|
/// that are entirely wrapped in a single set of round parens for multiline
|
||||||
|
/// items, and associate all comments with the next non-comment line
|
||||||
|
///
|
||||||
|
/// The parse result's [Parsed::output] is the comments, the line's contents are
|
||||||
|
/// held in [Parsed::tail] so semantically your line parser "continues" for each
|
||||||
|
/// line separately
|
||||||
|
///
|
||||||
|
/// This is the procedure for module parsing
|
||||||
pub async fn line_items<'a, A: ExprRepr, X: ExtraTok>(
|
pub async fn line_items<'a, A: ExprRepr, X: ExtraTok>(
|
||||||
ctx: &impl ParseCtx,
|
|
||||||
snip: Snippet<'a, A, X>,
|
snip: Snippet<'a, A, X>,
|
||||||
) -> Vec<Parsed<'a, Vec<Comment>, A, X>> {
|
) -> Vec<Parsed<'a, Vec<Comment>, A, X>> {
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
@@ -145,9 +159,10 @@ pub async fn line_items<'a, A: ExprRepr, X: ExtraTok>(
|
|||||||
None => comments.extend(line.cur),
|
None => comments.extend(line.cur),
|
||||||
Some(i) => {
|
Some(i) => {
|
||||||
let (cmts, tail) = line.split_at(i);
|
let (cmts, tail) = line.split_at(i);
|
||||||
let comments = join_all(comments.drain(..).chain(cmts.cur).map(|t| async {
|
let comments = join_all(
|
||||||
Comment::from_tk(t, ctx.i()).await.expect("All are comments checked above")
|
(comments.drain(..).chain(cmts.cur))
|
||||||
}))
|
.map(|t| async { Comment::from_tk(t).await.expect("All are comments checked above") }),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
items.push(Parsed { output: comments, tail });
|
items.push(Parsed { output: comments, tail });
|
||||||
},
|
},
|
||||||
@@ -156,27 +171,24 @@ pub async fn line_items<'a, A: ExprRepr, X: ExtraTok>(
|
|||||||
items
|
items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pop the next token that isn't a comment or line break
|
||||||
pub async fn try_pop_no_fluff<'a, A: ExprRepr, X: ExtraTok>(
|
pub async fn try_pop_no_fluff<'a, A: ExprRepr, X: ExtraTok>(
|
||||||
ctx: &impl ParseCtx,
|
|
||||||
snip: Snippet<'a, A, X>,
|
snip: Snippet<'a, A, X>,
|
||||||
) -> ParseRes<'a, &'a TokTree<A, X>, A, X> {
|
) -> ParseRes<'a, &'a TokTree<A, X>, A, X> {
|
||||||
match snip.skip_fluff().pop_front() {
|
match snip.skip_fluff().split_first() {
|
||||||
Some((output, tail)) => Ok(Parsed { output, tail }),
|
Some((output, tail)) => Ok(Parsed { output, tail }),
|
||||||
None => Err(mk_errv(
|
None =>
|
||||||
ctx.i().i("Unexpected end").await,
|
Err(mk_errv(is("Unexpected end").await, "Line ends abruptly; more tokens were expected", [
|
||||||
"Line ends abruptly; more tokens were expected",
|
snip.sr(),
|
||||||
[snip.sr()],
|
])),
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn expect_end(
|
/// Fail if the snippet isn't empty
|
||||||
ctx: &impl ParseCtx,
|
pub async fn expect_end(snip: Snippet<'_, impl ExprRepr, impl ExtraTok>) -> OrcRes<()> {
|
||||||
snip: Snippet<'_, impl ExprRepr, impl ExtraTok>,
|
|
||||||
) -> OrcRes<()> {
|
|
||||||
match snip.skip_fluff().get(0) {
|
match snip.skip_fluff().get(0) {
|
||||||
Some(surplus) => Err(mk_errv(
|
Some(surplus) => Err(mk_errv(
|
||||||
ctx.i().i("Extra code after end of line").await,
|
is("Extra code after end of line").await,
|
||||||
"Code found after the end of the line",
|
"Code found after the end of the line",
|
||||||
[surplus.sr.pos()],
|
[surplus.sr.pos()],
|
||||||
)),
|
)),
|
||||||
@@ -184,66 +196,65 @@ pub async fn expect_end(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a token and ensure that it matches the specified keyword
|
||||||
pub async fn expect_tok<'a, A: ExprRepr, X: ExtraTok>(
|
pub async fn expect_tok<'a, A: ExprRepr, X: ExtraTok>(
|
||||||
ctx: &impl ParseCtx,
|
|
||||||
snip: Snippet<'a, A, X>,
|
snip: Snippet<'a, A, X>,
|
||||||
tok: Tok<String>,
|
tok: IStr,
|
||||||
) -> ParseRes<'a, (), A, X> {
|
) -> ParseRes<'a, (), A, X> {
|
||||||
let Parsed { output: head, tail } = try_pop_no_fluff(ctx, snip).await?;
|
let Parsed { output: head, tail } = try_pop_no_fluff(snip).await?;
|
||||||
match &head.tok {
|
match &head.tok {
|
||||||
Token::Name(n) if *n == tok => Ok(Parsed { output: (), tail }),
|
Token::Name(n) if *n == tok => Ok(Parsed { output: (), tail }),
|
||||||
t => Err(mk_errv(
|
t => Err(mk_errv(
|
||||||
ctx.i().i("Expected specific keyword").await,
|
is("Expected specific keyword").await,
|
||||||
format!("Expected {tok} but found {:?}", fmt(t, ctx.i()).await),
|
format!("Expected {tok} but found {:?}", fmt(t).await),
|
||||||
[head.sr()],
|
[head.sr()],
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Report an error related to a token that can conveniently use the token's
|
||||||
|
/// text representation in the long message
|
||||||
pub async fn token_errv<A: ExprRepr, X: ExtraTok>(
|
pub async fn token_errv<A: ExprRepr, X: ExtraTok>(
|
||||||
ctx: &impl ParseCtx,
|
|
||||||
tok: &TokTree<A, X>,
|
tok: &TokTree<A, X>,
|
||||||
description: &'static str,
|
description: &'static str,
|
||||||
message: impl FnOnce(&str) -> String,
|
message: impl FnOnce(&str) -> String,
|
||||||
) -> OrcErrv {
|
) -> OrcErrv {
|
||||||
mk_errv(ctx.i().i(description).await, message(&fmt(tok, ctx.i()).await), [tok.sr.pos()])
|
mk_errv(is(description).await, message(&fmt(tok).await), [tok.sr.pos()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Success output of parsers
|
||||||
pub struct Parsed<'a, T, H: ExprRepr, X: ExtraTok> {
|
pub struct Parsed<'a, T, H: ExprRepr, X: ExtraTok> {
|
||||||
|
/// Information obtained from consumed tokens
|
||||||
pub output: T,
|
pub output: T,
|
||||||
|
/// Input to next parser
|
||||||
pub tail: Snippet<'a, H, X>,
|
pub tail: Snippet<'a, H, X>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ParseRes<'a, T, H, X> = OrcRes<Parsed<'a, T, H, X>>;
|
pub type ParseRes<'a, T, H, X> = OrcRes<Parsed<'a, T, H, X>>;
|
||||||
|
|
||||||
|
/// Parse a `namespaced::name` or a `namespaced::(multi name)`
|
||||||
pub async fn parse_multiname<'a, A: ExprRepr, X: ExtraTok>(
|
pub async fn parse_multiname<'a, A: ExprRepr, X: ExtraTok>(
|
||||||
ctx: &impl ParseCtx,
|
|
||||||
tail: Snippet<'a, A, X>,
|
tail: Snippet<'a, A, X>,
|
||||||
) -> ParseRes<'a, Vec<Import>, A, X> {
|
) -> ParseRes<'a, Vec<Import>, A, X> {
|
||||||
let Some((tt, tail)) = tail.skip_fluff().pop_front() else {
|
let Some((tt, tail)) = tail.skip_fluff().split_first() else {
|
||||||
return Err(mk_errv(
|
return Err(mk_errv(
|
||||||
ctx.i().i("Expected token").await,
|
is("Expected token").await,
|
||||||
"Expected a name, a parenthesized list of names, or a globstar.",
|
"Expected a name, a parenthesized list of names, or a globstar.",
|
||||||
[tail.sr().pos()],
|
[tail.sr().pos()],
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let ret = rec(tt, ctx).await;
|
let ret = rec(tt).await;
|
||||||
#[allow(clippy::type_complexity)] // it's an internal function
|
#[allow(clippy::type_complexity)] // it's an internal function
|
||||||
pub async fn rec<A: ExprRepr, X: ExtraTok>(
|
pub async fn rec<A: ExprRepr, X: ExtraTok>(
|
||||||
tt: &TokTree<A, X>,
|
tt: &TokTree<A, X>,
|
||||||
ctx: &impl ParseCtx,
|
) -> OrcRes<Vec<(Vec<IStr>, Option<IStr>, SrcRange)>> {
|
||||||
) -> OrcRes<Vec<(Vec<Tok<String>>, Option<Tok<String>>, SrcRange)>> {
|
|
||||||
let ttpos = tt.sr.pos();
|
let ttpos = tt.sr.pos();
|
||||||
match &tt.tok {
|
match &tt.tok {
|
||||||
Token::NS(ns, body) => {
|
Token::NS(ns, body) => {
|
||||||
if !ns.starts_with(name_start) {
|
if !ns.starts_with(name_start) {
|
||||||
ctx.rep().report(mk_errv(
|
report(mk_errv(is("Unexpected name prefix").await, "Only names can precede ::", [ttpos]))
|
||||||
ctx.i().i("Unexpected name prefix").await,
|
|
||||||
"Only names can precede ::",
|
|
||||||
[ttpos],
|
|
||||||
))
|
|
||||||
};
|
};
|
||||||
let out = Box::pin(rec(body, ctx)).await?;
|
let out = Box::pin(rec(body)).await?;
|
||||||
Ok(out.into_iter().update(|i| i.0.push(ns.clone())).collect_vec())
|
Ok(out.into_iter().update(|i| i.0.push(ns.clone())).collect_vec())
|
||||||
},
|
},
|
||||||
Token::Name(ntok) => {
|
Token::Name(ntok) => {
|
||||||
@@ -254,22 +265,20 @@ pub async fn parse_multiname<'a, A: ExprRepr, X: ExtraTok>(
|
|||||||
Token::S(Paren::Round, b) => {
|
Token::S(Paren::Round, b) => {
|
||||||
let mut o = Vec::new();
|
let mut o = Vec::new();
|
||||||
let mut body = Snippet::new(tt, b);
|
let mut body = Snippet::new(tt, b);
|
||||||
while let Some((output, tail)) = body.pop_front() {
|
while let Some((output, tail)) = body.split_first() {
|
||||||
match rec(output, ctx).boxed_local().await {
|
match rec(output).boxed_local().await {
|
||||||
Ok(names) => o.extend(names),
|
Ok(names) => o.extend(names),
|
||||||
Err(e) => ctx.rep().report(e),
|
Err(e) => report(e),
|
||||||
}
|
}
|
||||||
body = tail;
|
body = tail;
|
||||||
}
|
}
|
||||||
Ok(o)
|
Ok(o)
|
||||||
},
|
},
|
||||||
t => {
|
t => Err(mk_errv(
|
||||||
return Err(mk_errv(
|
is("Unrecognized name end").await,
|
||||||
ctx.i().i("Unrecognized name end").await,
|
format!("Names cannot end with {:?} tokens", fmt(t).await),
|
||||||
format!("Names cannot end with {:?} tokens", fmt(t, ctx.i()).await),
|
|
||||||
[ttpos],
|
[ttpos],
|
||||||
));
|
)),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ret.map(|output| {
|
ret.map(|output| {
|
||||||
@@ -285,7 +294,7 @@ pub async fn parse_multiname<'a, A: ExprRepr, X: ExtraTok>(
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Import {
|
pub struct Import {
|
||||||
pub path: VPath,
|
pub path: VPath,
|
||||||
pub name: Option<Tok<String>>,
|
pub name: Option<IStr>,
|
||||||
pub sr: SrcRange,
|
pub sr: SrcRange,
|
||||||
}
|
}
|
||||||
impl Import {
|
impl Import {
|
||||||
@@ -296,14 +305,14 @@ impl Import {
|
|||||||
None => self.path.into_name().expect("Import cannot be empty"),
|
None => self.path.into_name().expect("Import cannot be empty"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new(sr: SrcRange, path: VPath, name: Tok<String>) -> Self {
|
pub fn new(sr: SrcRange, path: VPath, name: IStr) -> Self {
|
||||||
Import { path, name: Some(name), sr }
|
Import { path, name: Some(name), sr }
|
||||||
}
|
}
|
||||||
pub fn new_glob(sr: SrcRange, path: VPath) -> Self { Import { path, name: None, sr } }
|
pub fn new_glob(sr: SrcRange, path: VPath) -> Self { Import { path, name: None, sr } }
|
||||||
}
|
}
|
||||||
impl Display for Import {
|
impl Display for Import {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}::{}", self.path.iter().join("::"), self.name.as_ref().map_or("*", |t| t.as_str()))
|
write!(f, "{}::{}", self.path.iter().join("::"), self.name.as_ref().map_or("*", |t| &**t))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
//! Methods to operate on Rust vectors in a declarative manner
|
|
||||||
|
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
/// Pure version of [Vec::push]
|
|
||||||
///
|
|
||||||
/// Create a new vector consisting of the provided vector with the
|
|
||||||
/// element appended. See [pushed_ref] to use it with a slice
|
|
||||||
pub fn pushed<I: IntoIterator, C: FromIterator<I::Item>>(vec: I, t: I::Item) -> C {
|
|
||||||
vec.into_iter().chain(iter::once(t)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pure version of [Vec::push]
|
|
||||||
///
|
|
||||||
/// Create a new vector consisting of the provided slice with the
|
|
||||||
/// element appended. See [pushed] for the owned version
|
|
||||||
pub fn pushed_ref<'a, T: Clone + 'a, C: FromIterator<T>>(
|
|
||||||
vec: impl IntoIterator<Item = &'a T>,
|
|
||||||
t: T,
|
|
||||||
) -> C {
|
|
||||||
vec.into_iter().cloned().chain(iter::once(t)).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push an element on the adhoc stack, pass it to the callback, then pop the
|
|
||||||
/// element out again.
|
|
||||||
pub fn with_pushed<T, U>(
|
|
||||||
vec: &mut Vec<T>,
|
|
||||||
item: T,
|
|
||||||
cb: impl for<'a> FnOnce(&'a mut Vec<T>) -> U,
|
|
||||||
) -> (T, U) {
|
|
||||||
vec.push(item);
|
|
||||||
let out = cb(vec);
|
|
||||||
let item = vec.pop().expect("top element stolen by callback");
|
|
||||||
(item, out)
|
|
||||||
}
|
|
||||||
@@ -1,342 +0,0 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::mem;
|
|
||||||
use std::ops::{BitAnd, Deref};
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
|
|
||||||
use derive_destructure::destructure;
|
|
||||||
use dyn_clone::{DynClone, clone_box};
|
|
||||||
use futures::channel::mpsc;
|
|
||||||
use futures::future::LocalBoxFuture;
|
|
||||||
use futures::lock::Mutex;
|
|
||||||
use futures::{SinkExt, StreamExt};
|
|
||||||
use hashbrown::HashMap;
|
|
||||||
use orchid_api_traits::{Channel, Coding, Decode, Encode, MsgSet, Request};
|
|
||||||
use trait_set::trait_set;
|
|
||||||
|
|
||||||
use crate::clone;
|
|
||||||
use crate::logging::Logger;
|
|
||||||
|
|
||||||
pub struct Receipt<'a>(PhantomData<&'a mut ()>);
|
|
||||||
|
|
||||||
trait_set! {
|
|
||||||
pub trait SendFn<T: MsgSet> =
|
|
||||||
for<'a> FnMut(&'a [u8], ReqNot<T>) -> LocalBoxFuture<'a, ()>
|
|
||||||
+ DynClone + 'static;
|
|
||||||
pub trait ReqFn<T: MsgSet> =
|
|
||||||
for<'a> FnMut(RequestHandle<'a, T>, <T::In as Channel>::Req)
|
|
||||||
-> LocalBoxFuture<'a, Receipt<'a>>
|
|
||||||
+ DynClone + 'static;
|
|
||||||
pub trait NotifFn<T: MsgSet> =
|
|
||||||
FnMut(<T::In as Channel>::Notif, ReqNot<T>) -> LocalBoxFuture<'static, ()>
|
|
||||||
+ DynClone + 'static;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_id(message: &[u8]) -> (u64, &[u8]) {
|
|
||||||
(u64::from_be_bytes(message[..8].to_vec().try_into().unwrap()), &message[8..])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ReqHandlish {
|
|
||||||
fn defer(&self, cb: impl Future<Output = ()> + 'static)
|
|
||||||
where Self: Sized {
|
|
||||||
self.defer_objsafe(Box::pin(cb));
|
|
||||||
}
|
|
||||||
fn defer_objsafe(&self, val: Pin<Box<dyn Future<Output = ()>>>);
|
|
||||||
}
|
|
||||||
impl ReqHandlish for &'_ dyn ReqHandlish {
|
|
||||||
fn defer_objsafe(&self, val: Pin<Box<dyn Future<Output = ()>>>) { (**self).defer_objsafe(val) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(destructure)]
|
|
||||||
pub struct RequestHandle<'a, MS: MsgSet> {
|
|
||||||
defer: RefCell<Vec<Pin<Box<dyn Future<Output = ()>>>>>,
|
|
||||||
fulfilled: AtomicBool,
|
|
||||||
id: u64,
|
|
||||||
_reqlt: PhantomData<&'a mut ()>,
|
|
||||||
parent: ReqNot<MS>,
|
|
||||||
}
|
|
||||||
impl<'a, MS: MsgSet + 'static> RequestHandle<'a, MS> {
|
|
||||||
fn new(parent: ReqNot<MS>, id: u64) -> Self {
|
|
||||||
Self { defer: RefCell::default(), fulfilled: false.into(), _reqlt: PhantomData, parent, id }
|
|
||||||
}
|
|
||||||
pub fn reqnot(&self) -> ReqNot<MS> { self.parent.clone() }
|
|
||||||
pub async fn handle<U: Request>(&self, _: &U, rep: &U::Response) -> Receipt<'a> {
|
|
||||||
self.respond(rep).await
|
|
||||||
}
|
|
||||||
pub fn will_handle_as<U: Request>(&self, _: &U) -> ReqTypToken<U> { ReqTypToken(PhantomData) }
|
|
||||||
pub async fn handle_as<U: Request>(&self, _: ReqTypToken<U>, rep: &U::Response) -> Receipt<'a> {
|
|
||||||
self.respond(rep).await
|
|
||||||
}
|
|
||||||
pub async fn respond(&self, response: &impl Encode) -> Receipt<'a> {
|
|
||||||
assert!(!self.fulfilled.swap(true, Ordering::Relaxed), "Already responded to {}", self.id);
|
|
||||||
let mut buf = (!self.id).to_be_bytes().to_vec();
|
|
||||||
response.encode(Pin::new(&mut buf)).await;
|
|
||||||
let mut send = clone_box(&*self.reqnot().0.lock().await.send);
|
|
||||||
(send)(&buf, self.parent.clone()).await;
|
|
||||||
let deferred = mem::take(&mut *self.defer.borrow_mut());
|
|
||||||
for item in deferred {
|
|
||||||
item.await
|
|
||||||
}
|
|
||||||
Receipt(PhantomData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<MS: MsgSet> ReqHandlish for RequestHandle<'_, MS> {
|
|
||||||
fn defer_objsafe(&self, val: Pin<Box<dyn Future<Output = ()>>>) {
|
|
||||||
self.defer.borrow_mut().push(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<MS: MsgSet> Drop for RequestHandle<'_, MS> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let done = self.fulfilled.load(Ordering::Relaxed);
|
|
||||||
debug_assert!(done, "Request {} dropped without response", self.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ReqTypToken<T>(PhantomData<T>);
|
|
||||||
|
|
||||||
pub struct ReqNotData<T: MsgSet> {
|
|
||||||
id: u64,
|
|
||||||
send: Box<dyn SendFn<T>>,
|
|
||||||
notif: Box<dyn NotifFn<T>>,
|
|
||||||
req: Box<dyn ReqFn<T>>,
|
|
||||||
responses: HashMap<u64, mpsc::Sender<Vec<u8>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wraps a raw message buffer to save on copying.
|
|
||||||
/// Dereferences to the tail of the message buffer, cutting off the ID
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RawReply(Vec<u8>);
|
|
||||||
impl Deref for RawReply {
|
|
||||||
type Target = [u8];
|
|
||||||
fn deref(&self) -> &Self::Target { get_id(&self.0[..]).1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ReqNot<T: MsgSet>(Arc<Mutex<ReqNotData<T>>>, Logger);
|
|
||||||
impl<T: MsgSet> ReqNot<T> {
|
|
||||||
pub fn new(
|
|
||||||
logger: Logger,
|
|
||||||
send: impl SendFn<T>,
|
|
||||||
notif: impl NotifFn<T>,
|
|
||||||
req: impl ReqFn<T>,
|
|
||||||
) -> Self {
|
|
||||||
Self(
|
|
||||||
Arc::new(Mutex::new(ReqNotData {
|
|
||||||
id: 1,
|
|
||||||
send: Box::new(send),
|
|
||||||
notif: Box::new(notif),
|
|
||||||
req: Box::new(req),
|
|
||||||
responses: HashMap::new(),
|
|
||||||
})),
|
|
||||||
logger,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Can be called from a polling thread or dispatched in any other way
|
|
||||||
pub async fn receive(&self, message: &[u8]) {
|
|
||||||
let mut g = self.0.lock().await;
|
|
||||||
let (id, payload) = get_id(message);
|
|
||||||
if id == 0 {
|
|
||||||
let mut notif_cb = clone_box(&*g.notif);
|
|
||||||
mem::drop(g);
|
|
||||||
let notif_val = <T::In as Channel>::Notif::decode(Pin::new(&mut &payload[..])).await;
|
|
||||||
notif_cb(notif_val, self.clone()).await
|
|
||||||
} else if 0 < id.bitand(1 << 63) {
|
|
||||||
let mut sender = g.responses.remove(&!id).expect("Received response for invalid message");
|
|
||||||
let _ = sender.send(message.to_vec()).await;
|
|
||||||
} else {
|
|
||||||
let message = <T::In as Channel>::Req::decode(Pin::new(&mut &payload[..])).await;
|
|
||||||
let mut req_cb = clone_box(&*g.req);
|
|
||||||
mem::drop(g);
|
|
||||||
let rn = self.clone();
|
|
||||||
req_cb(RequestHandle::new(rn, id), message).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn notify<N: Coding + Into<<T::Out as Channel>::Notif>>(&self, notif: N) {
|
|
||||||
let mut send = clone_box(&*self.0.lock().await.send);
|
|
||||||
let mut buf = vec![0; 8];
|
|
||||||
let msg: <T::Out as Channel>::Notif = notif.into();
|
|
||||||
msg.encode(Pin::new(&mut buf)).await;
|
|
||||||
send(&buf, self.clone()).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait DynRequester {
|
|
||||||
type Transfer;
|
|
||||||
fn logger(&self) -> &Logger;
|
|
||||||
/// Encode and send a request, then receive the response buffer.
|
|
||||||
fn raw_request(&self, data: Self::Transfer) -> LocalBoxFuture<'_, RawReply>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MappedRequester<'a, T: 'a>(Box<dyn Fn(T) -> LocalBoxFuture<'a, RawReply> + 'a>, Logger);
|
|
||||||
impl<'a, T> MappedRequester<'a, T> {
|
|
||||||
fn new<U: DynRequester + 'a, F: Fn(T) -> U::Transfer + 'a>(
|
|
||||||
req: U,
|
|
||||||
cb: F,
|
|
||||||
logger: Logger,
|
|
||||||
) -> Self {
|
|
||||||
let req_arc = Arc::new(req);
|
|
||||||
let cb_arc = Arc::new(cb);
|
|
||||||
MappedRequester(
|
|
||||||
Box::new(move |t| {
|
|
||||||
Box::pin(clone!(req_arc, cb_arc; async move { req_arc.raw_request(cb_arc(t)).await}))
|
|
||||||
}),
|
|
||||||
logger,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DynRequester for MappedRequester<'_, T> {
|
|
||||||
type Transfer = T;
|
|
||||||
fn logger(&self) -> &Logger { &self.1 }
|
|
||||||
fn raw_request(&self, data: Self::Transfer) -> LocalBoxFuture<'_, RawReply> { self.0(data) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: MsgSet> DynRequester for ReqNot<T> {
|
|
||||||
type Transfer = <T::Out as Channel>::Req;
|
|
||||||
fn logger(&self) -> &Logger { &self.1 }
|
|
||||||
fn raw_request(&self, req: Self::Transfer) -> LocalBoxFuture<'_, RawReply> {
|
|
||||||
Box::pin(async move {
|
|
||||||
let mut g = self.0.lock().await;
|
|
||||||
let id = g.id;
|
|
||||||
g.id += 1;
|
|
||||||
let mut buf = id.to_be_bytes().to_vec();
|
|
||||||
req.encode(Pin::new(&mut buf)).await;
|
|
||||||
let (send, mut recv) = mpsc::channel(1);
|
|
||||||
g.responses.insert(id, send);
|
|
||||||
let mut send = clone_box(&*g.send);
|
|
||||||
mem::drop(g);
|
|
||||||
let rn = self.clone();
|
|
||||||
send(&buf, rn).await;
|
|
||||||
let items = recv.next().await;
|
|
||||||
RawReply(items.unwrap())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Requester: DynRequester {
|
|
||||||
#[must_use = "These types are subject to change with protocol versions. \
|
|
||||||
If you don't want to use the return value, At a minimum, force the type."]
|
|
||||||
fn request<R: Request + Into<Self::Transfer>>(
|
|
||||||
&self,
|
|
||||||
data: R,
|
|
||||||
) -> impl Future<Output = R::Response>;
|
|
||||||
fn map<'a, U>(self, cb: impl Fn(U) -> Self::Transfer + 'a) -> MappedRequester<'a, U>
|
|
||||||
where Self: Sized + 'a {
|
|
||||||
let logger = self.logger().clone();
|
|
||||||
MappedRequester::new(self, cb, logger)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<This: DynRequester + ?Sized> Requester for This {
|
|
||||||
async fn request<R: Request + Into<Self::Transfer>>(&self, data: R) -> R::Response {
|
|
||||||
let req = format!("{data:?}");
|
|
||||||
let rep = R::Response::decode(Pin::new(&mut &self.raw_request(data.into()).await[..])).await;
|
|
||||||
let req_str = req.to_string();
|
|
||||||
if !req_str.starts_with("AtomPrint") && !req_str.starts_with("ExtAtomPrint") {
|
|
||||||
writeln!(self.logger(), "Request {req} got response {rep:?}");
|
|
||||||
}
|
|
||||||
rep
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: MsgSet> Clone for ReqNot<T> {
|
|
||||||
fn clone(&self) -> Self { Self(self.0.clone(), self.1.clone()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use futures::FutureExt;
|
|
||||||
use futures::lock::Mutex;
|
|
||||||
use orchid_api_derive::Coding;
|
|
||||||
use orchid_api_traits::{Channel, Request};
|
|
||||||
use test_executors::spin_on;
|
|
||||||
|
|
||||||
use super::{MsgSet, ReqNot};
|
|
||||||
use crate::logging::Logger;
|
|
||||||
use crate::reqnot::Requester as _;
|
|
||||||
use crate::{api, clone};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Coding, PartialEq)]
|
|
||||||
pub struct TestReq(u8);
|
|
||||||
impl Request for TestReq {
|
|
||||||
type Response = u8;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TestChan;
|
|
||||||
impl Channel for TestChan {
|
|
||||||
type Notif = u8;
|
|
||||||
type Req = TestReq;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TestMsgSet;
|
|
||||||
impl MsgSet for TestMsgSet {
|
|
||||||
type In = TestChan;
|
|
||||||
type Out = TestChan;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn notification() {
|
|
||||||
spin_on(async {
|
|
||||||
let logger = Logger::new(api::LogStrategy::StdErr);
|
|
||||||
let received = Arc::new(Mutex::new(None));
|
|
||||||
let receiver = ReqNot::<TestMsgSet>::new(
|
|
||||||
logger.clone(),
|
|
||||||
|_, _| panic!("Should not send anything"),
|
|
||||||
clone!(received; move |notif, _| clone!(received; async move {
|
|
||||||
*received.lock().await = Some(notif);
|
|
||||||
}.boxed_local())),
|
|
||||||
|_, _| panic!("Not receiving a request"),
|
|
||||||
);
|
|
||||||
let sender = ReqNot::<TestMsgSet>::new(
|
|
||||||
logger,
|
|
||||||
clone!(receiver; move |d, _| clone!(receiver; Box::pin(async move {
|
|
||||||
receiver.receive(d).await
|
|
||||||
}))),
|
|
||||||
|_, _| panic!("Should not receive notif"),
|
|
||||||
|_, _| panic!("Should not receive request"),
|
|
||||||
);
|
|
||||||
sender.notify(3).await;
|
|
||||||
assert_eq!(*received.lock().await, Some(3));
|
|
||||||
sender.notify(4).await;
|
|
||||||
assert_eq!(*received.lock().await, Some(4));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn request() {
|
|
||||||
spin_on(async {
|
|
||||||
let logger = Logger::new(api::LogStrategy::StdErr);
|
|
||||||
let receiver = Rc::new(Mutex::<Option<ReqNot<TestMsgSet>>>::new(None));
|
|
||||||
let sender = Rc::new(ReqNot::<TestMsgSet>::new(
|
|
||||||
logger.clone(),
|
|
||||||
clone!(receiver; move |d, _| clone!(receiver; Box::pin(async move {
|
|
||||||
receiver.lock().await.as_ref().unwrap().receive(d).await
|
|
||||||
}))),
|
|
||||||
|_, _| panic!("Should not receive notif"),
|
|
||||||
|_, _| panic!("Should not receive request"),
|
|
||||||
));
|
|
||||||
*receiver.lock().await = Some(ReqNot::new(
|
|
||||||
logger,
|
|
||||||
clone!(sender; move |d, _| clone!(sender; Box::pin(async move {
|
|
||||||
sender.receive(d).await
|
|
||||||
}))),
|
|
||||||
|_, _| panic!("Not receiving notifs"),
|
|
||||||
|hand, req| {
|
|
||||||
Box::pin(async move {
|
|
||||||
assert_eq!(req, TestReq(5));
|
|
||||||
hand.respond(&6u8).await
|
|
||||||
})
|
|
||||||
},
|
|
||||||
));
|
|
||||||
let response = sender.request(TestReq(5)).await;
|
|
||||||
assert_eq!(response, 6);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
//! An alternative to `Iterable` in many languages, a [Fn] that returns an
|
|
||||||
//! iterator.
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use trait_set::trait_set;
|
|
||||||
|
|
||||||
use super::boxed_iter::BoxedIter;
|
|
||||||
|
|
||||||
trait_set! {
|
|
||||||
trait Payload<'a, T> = Fn() -> BoxedIter<'a, T> + 'a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dynamic iterator building callback. Given how many trait objects this
|
|
||||||
/// involves, it may actually be slower than C#.
|
|
||||||
pub struct Sequence<'a, T: 'a>(Rc<dyn Payload<'a, T>>);
|
|
||||||
impl<'a, T: 'a> Sequence<'a, T> {
|
|
||||||
/// Construct from a concrete function returning a concrete iterator
|
|
||||||
pub fn new<I: IntoIterator<Item = T> + 'a>(f: impl Fn() -> I + 'a) -> Self {
|
|
||||||
Self(Rc::new(move || Box::new(f().into_iter())))
|
|
||||||
}
|
|
||||||
/// Get an iterator from the function
|
|
||||||
pub fn iter(&self) -> BoxedIter<'_, T> { (self.0)() }
|
|
||||||
}
|
|
||||||
impl<'a, T: 'a> Clone for Sequence<'a, T> {
|
|
||||||
fn clone(&self) -> Self { Self(self.0.clone()) }
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::Not;
|
use std::ops::Not;
|
||||||
|
|
||||||
use crate::boxed_iter::BoxedIter;
|
use itertools::Either;
|
||||||
|
|
||||||
/// A primitive for encoding the two sides Left and Right. While booleans
|
/// A primitive for encoding the two sides Left and Right. While booleans
|
||||||
/// are technically usable for this purpose, they're very easy to confuse
|
/// are technically usable for this purpose, they're very easy to confuse
|
||||||
@@ -67,10 +67,13 @@ impl Side {
|
|||||||
}
|
}
|
||||||
/// Walk a double ended iterator (assumed to be left-to-right) in this
|
/// Walk a double ended iterator (assumed to be left-to-right) in this
|
||||||
/// direction
|
/// direction
|
||||||
pub fn walk<'a, I: DoubleEndedIterator + 'a>(&self, iter: I) -> BoxedIter<'a, I::Item> {
|
pub fn walk<'a, I: DoubleEndedIterator + 'a>(
|
||||||
|
&self,
|
||||||
|
iter: I,
|
||||||
|
) -> impl Iterator<Item = I::Item> + 'a {
|
||||||
match self {
|
match self {
|
||||||
Side::Right => Box::new(iter) as BoxedIter<I::Item>,
|
Side::Right => Either::Right(iter),
|
||||||
Side::Left => Box::new(iter.rev()),
|
Side::Left => Either::Left(iter.rev()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
orchid-base/src/stash.rs
Normal file
44
orchid-base/src/stash.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//! A pattern for running async code from sync destructors and other
|
||||||
|
//! unfortunately sync callbacks
|
||||||
|
//!
|
||||||
|
//! We create a task_local vecdeque which is moved into a thread_local whenever
|
||||||
|
//! the task is being polled. A call to [stash] pushes the future onto this
|
||||||
|
//! deque. Before [with_stash] returns, it pops everything from the deque
|
||||||
|
//! individually and awaits each of them, pushing any additionally stashed
|
||||||
|
//! futures onto the back of the same deque.
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use task_local::task_local;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct StashedFutures {
|
||||||
|
queue: RefCell<VecDeque<Pin<Box<dyn Future<Output = ()>>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
task_local! {
|
||||||
|
static STASHED_FUTURES: StashedFutures;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Complete the argument future, and any futures spawned from it via [stash].
|
||||||
|
/// This is useful mostly to guarantee that messaging destructors have run.
|
||||||
|
pub async fn with_stash<F: Future>(fut: F) -> F::Output {
|
||||||
|
STASHED_FUTURES
|
||||||
|
.scope(StashedFutures::default(), async {
|
||||||
|
let val = fut.await;
|
||||||
|
while let Some(fut) = STASHED_FUTURES.with(|sf| sf.queue.borrow_mut().pop_front()) {
|
||||||
|
fut.await;
|
||||||
|
}
|
||||||
|
val
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Schedule a future to be run before the next [with_stash] guard ends. This is
|
||||||
|
/// most useful for sending messages from destructors.
|
||||||
|
pub fn stash<F: Future<Output = ()> + 'static>(fut: F) {
|
||||||
|
(STASHED_FUTURES.try_with(|sf| sf.queue.borrow_mut().push_back(Box::pin(fut))))
|
||||||
|
.expect("No stash! Timely completion cannot be guaranteed")
|
||||||
|
}
|
||||||
@@ -1,3 +1,18 @@
|
|||||||
|
/// Cache a value in a [thread_local!]. Supports synchronous and asynchronous
|
||||||
|
/// initializers
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #[macro_use]
|
||||||
|
/// use orchid_base::tl_cache;
|
||||||
|
///
|
||||||
|
/// // simple synchronous case
|
||||||
|
/// let foo = tl_cache!(Rc<Vec<usize>>: vec![0; 1024]);
|
||||||
|
/// async {
|
||||||
|
/// async fn complex_operation(x: usize) -> usize { x + 1 }
|
||||||
|
/// // async case
|
||||||
|
/// let bar = tl_cache!(async usize: complex_operation(0).await)
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! tl_cache {
|
macro_rules! tl_cache {
|
||||||
($ty:ty : $expr:expr) => {{
|
($ty:ty : $expr:expr) => {{
|
||||||
@@ -6,4 +21,18 @@ macro_rules! tl_cache {
|
|||||||
}
|
}
|
||||||
V.with(|v| v.clone())
|
V.with(|v| v.clone())
|
||||||
}};
|
}};
|
||||||
|
(async $ty:ty : $expr:expr) => {{
|
||||||
|
type CellType = std::cell::OnceCell<$ty>;
|
||||||
|
thread_local! {
|
||||||
|
static V: CellType = std::cell::OnceCell::default();
|
||||||
|
}
|
||||||
|
match V.with(|cell: &CellType| cell.get().cloned()) {
|
||||||
|
Some(val) => val as $ty,
|
||||||
|
None => {
|
||||||
|
let val = $expr;
|
||||||
|
let _ = V.with(|cell: &CellType| cell.set(val.clone()));
|
||||||
|
val as $ty
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
pub use api::Paren;
|
|
||||||
|
|
||||||
use crate::api;
|
|
||||||
|
|
||||||
pub const PARENS: &[(char, char, Paren)] =
|
|
||||||
&[('(', ')', Paren::Round), ('[', ']', Paren::Square), ('{', '}', Paren::Curly)];
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::borrow::Borrow;
|
|
||||||
use std::fmt::{self, Debug, Display};
|
use std::fmt::{self, Debug, Display};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
@@ -12,63 +11,67 @@ use never::Never;
|
|||||||
use orchid_api_traits::Coding;
|
use orchid_api_traits::Coding;
|
||||||
use trait_set::trait_set;
|
use trait_set::trait_set;
|
||||||
|
|
||||||
use crate::error::OrcErrv;
|
use crate::{
|
||||||
use crate::format::{FmtCtx, FmtUnit, Format, Variants};
|
FmtCtx, FmtUnit, Format, IStr, OrcErrv, Pos, Snippet, SrcRange, Sym, VName, VPath, Variants, api,
|
||||||
use crate::interner::{Interner, Tok};
|
es, match_mapping, tl_cache,
|
||||||
use crate::location::{Pos, SrcRange};
|
};
|
||||||
use crate::name::{Sym, VName, VPath};
|
|
||||||
use crate::parse::Snippet;
|
|
||||||
use crate::{api, match_mapping, tl_cache};
|
|
||||||
|
|
||||||
|
/// The 3 types of parentheses Orchid's lexer recognizes as intrinsic groups in
|
||||||
|
/// the S-tree
|
||||||
|
pub type Paren = api::Paren;
|
||||||
|
|
||||||
|
/// Helper table with different kinds of parentheses recognized by the language.
|
||||||
|
/// opening, closing, variant name
|
||||||
|
pub const PARENS: &[(char, char, Paren)] =
|
||||||
|
&[('(', ')', Paren::Round), ('[', ']', Paren::Square), ('{', '}', Paren::Curly)];
|
||||||
|
|
||||||
|
/// Extension interface for embedded expressions and expression construction
|
||||||
|
/// commands inside token trees
|
||||||
pub trait TokenVariant<ApiEquiv: Clone + Debug + Coding>: Format + Clone + fmt::Debug {
|
pub trait TokenVariant<ApiEquiv: Clone + Debug + Coding>: Format + Clone + fmt::Debug {
|
||||||
|
/// Additional arguments to the deserializer. If deserialization of a token
|
||||||
|
/// type is impossible, set this to a sentinel unit type that describes why.
|
||||||
|
/// If you set this to [Never], your token tree type can never be
|
||||||
|
/// deserialized.
|
||||||
type FromApiCtx<'a>;
|
type FromApiCtx<'a>;
|
||||||
|
/// Additional arguments to the serializer. If serialization of a token type
|
||||||
|
/// is forbidden, set this to a sentinel unit type that describes how to avoid
|
||||||
|
/// it.
|
||||||
|
/// If you set this to [Never], your token tree type can never be serialized.
|
||||||
type ToApiCtx<'a>;
|
type ToApiCtx<'a>;
|
||||||
|
/// Deserializer
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn from_api(
|
fn from_api(
|
||||||
api: &ApiEquiv,
|
api: ApiEquiv,
|
||||||
ctx: &mut Self::FromApiCtx<'_>,
|
ctx: &mut Self::FromApiCtx<'_>,
|
||||||
pos: SrcRange,
|
pos: SrcRange,
|
||||||
i: &Interner,
|
|
||||||
) -> impl Future<Output = Self>;
|
) -> impl Future<Output = Self>;
|
||||||
|
/// Serializer
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn into_api(self, ctx: &mut Self::ToApiCtx<'_>) -> impl Future<Output = ApiEquiv>;
|
fn into_api(self, ctx: &mut Self::ToApiCtx<'_>) -> impl Future<Output = ApiEquiv>;
|
||||||
}
|
}
|
||||||
impl<T: Clone + Debug + Coding> TokenVariant<T> for Never {
|
impl<T: Clone + Debug + Coding> TokenVariant<T> for Never {
|
||||||
type FromApiCtx<'a> = ();
|
type FromApiCtx<'a> = ();
|
||||||
type ToApiCtx<'a> = ();
|
type ToApiCtx<'a> = ();
|
||||||
async fn from_api(_: &T, _: &mut Self::FromApiCtx<'_>, _: SrcRange, _: &Interner) -> Self {
|
async fn from_api(_: T, _: &mut Self::FromApiCtx<'_>, _: SrcRange) -> Self {
|
||||||
panic!("Cannot deserialize Never")
|
panic!("Cannot deserialize Never")
|
||||||
}
|
}
|
||||||
async fn into_api(self, _: &mut Self::ToApiCtx<'_>) -> T { match self {} }
|
async fn into_api(self, _: &mut Self::ToApiCtx<'_>) -> T { match self {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
trait_set! {
|
trait_set! {
|
||||||
// TokenHandle
|
/// [api::Token::Handle] variant
|
||||||
pub trait ExprRepr = TokenVariant<api::ExprTicket>;
|
pub trait ExprRepr = TokenVariant<api::ExprTicket>;
|
||||||
// TokenExpr
|
/// [api::Token::NewExpr] variant
|
||||||
pub trait ExtraTok = TokenVariant<api::Expression>;
|
pub trait ExtraTok = TokenVariant<api::Expression>;
|
||||||
}
|
}
|
||||||
|
|
||||||
trait_set! {
|
trait_set! {
|
||||||
|
/// Callback to callback to [recur].
|
||||||
pub trait RecurCB<H: ExprRepr, X: ExtraTok> = Fn(TokTree<H, X>) -> TokTree<H, X>;
|
pub trait RecurCB<H: ExprRepr, X: ExtraTok> = Fn(TokTree<H, X>) -> TokTree<H, X>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recur<H: ExprRepr, X: ExtraTok>(
|
/// An atom that can be passed through the API boundary as part of an
|
||||||
tt: TokTree<H, X>,
|
/// expression. In particular, atoms created by extensions use this form.
|
||||||
f: &impl Fn(TokTree<H, X>, &dyn RecurCB<H, X>) -> TokTree<H, X>,
|
|
||||||
) -> TokTree<H, X> {
|
|
||||||
f(tt, &|TokTree { sr: range, tok }| {
|
|
||||||
let tok = match tok {
|
|
||||||
tok @ (Token::BR | Token::Bottom(_) | Token::Comment(_) | Token::Name(_)) => tok,
|
|
||||||
tok @ (Token::Handle(_) | Token::NewExpr(_)) => tok,
|
|
||||||
Token::NS(n, b) => Token::NS(n, Box::new(recur(*b, f))),
|
|
||||||
Token::LambdaHead(arg) => Token::LambdaHead(Box::new(recur(*arg, f))),
|
|
||||||
Token::S(p, b) => Token::S(p, b.into_iter().map(|tt| recur(tt, f)).collect_vec()),
|
|
||||||
};
|
|
||||||
TokTree { sr: range, tok }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AtomRepr: Clone + Format {
|
pub trait AtomRepr: Clone + Format {
|
||||||
type Ctx: ?Sized;
|
type Ctx: ?Sized;
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -94,6 +97,7 @@ impl Display for TokHandle<'_> {
|
|||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Handle({})", self.0.0) }
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Handle({})", self.0.0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lexer output
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TokTree<H: ExprRepr, X: ExtraTok> {
|
pub struct TokTree<H: ExprRepr, X: ExtraTok> {
|
||||||
pub tok: Token<H, X>,
|
pub tok: Token<H, X>,
|
||||||
@@ -103,25 +107,39 @@ pub struct TokTree<H: ExprRepr, X: ExtraTok> {
|
|||||||
pub sr: SrcRange,
|
pub sr: SrcRange,
|
||||||
}
|
}
|
||||||
impl<H: ExprRepr, X: ExtraTok> TokTree<H, X> {
|
impl<H: ExprRepr, X: ExtraTok> TokTree<H, X> {
|
||||||
|
/// Visit all tokens, modify them at will, and optionally recurse into them by
|
||||||
|
/// calling the callback passed to your callback
|
||||||
|
pub fn recur(self, f: &impl Fn(Self, &dyn RecurCB<H, X>) -> Self) -> Self {
|
||||||
|
f(self, &|TokTree { sr: range, tok }| {
|
||||||
|
let tok = match tok {
|
||||||
|
tok @ (Token::BR | Token::Bottom(_) | Token::Comment(_) | Token::Name(_)) => tok,
|
||||||
|
tok @ (Token::Handle(_) | Token::NewExpr(_)) => tok,
|
||||||
|
Token::NS(n, b) => Token::NS(n, Box::new(b.recur(f))),
|
||||||
|
Token::LambdaHead(arg) => Token::LambdaHead(Box::new(arg.recur(f))),
|
||||||
|
Token::S(p, b) => Token::S(p, b.into_iter().map(|tt| tt.recur(f)).collect_vec()),
|
||||||
|
};
|
||||||
|
TokTree { sr: range, tok }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn from_api(
|
pub async fn from_api(
|
||||||
tt: &api::TokenTree,
|
tt: api::TokenTree,
|
||||||
hctx: &mut H::FromApiCtx<'_>,
|
hctx: &mut H::FromApiCtx<'_>,
|
||||||
xctx: &mut X::FromApiCtx<'_>,
|
xctx: &mut X::FromApiCtx<'_>,
|
||||||
src: &Sym,
|
src: &Sym,
|
||||||
i: &Interner,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let pos = SrcRange::new(tt.range.clone(), src);
|
let pos = SrcRange::new(tt.range, src);
|
||||||
let tok = match_mapping!(&tt.token, api::Token => Token::<H, X> {
|
let tok = match_mapping!(tt.token, api::Token => Token::<H, X> {
|
||||||
BR,
|
BR,
|
||||||
NS(n => Tok::from_api(*n, i).await,
|
NS(n => es(n).await,
|
||||||
b => Box::new(Self::from_api(b, hctx, xctx, src, i).boxed_local().await)),
|
b => Box::new(Self::from_api(*b, hctx, xctx, src).boxed_local().await)),
|
||||||
Bottom(e => OrcErrv::from_api(e, i).await),
|
Bottom(e => OrcErrv::from_api(e).await),
|
||||||
LambdaHead(arg => Box::new(Self::from_api(arg, hctx, xctx, src, i).boxed_local().await)),
|
LambdaHead(arg => Box::new(Self::from_api(*arg, hctx, xctx, src).boxed_local().await)),
|
||||||
Name(n => Tok::from_api(*n, i).await),
|
Name(n => es(n).await),
|
||||||
S(*par, b => ttv_from_api(b, hctx, xctx, src, i).await),
|
S(par, b => ttv_from_api(b, hctx, xctx, src).await),
|
||||||
Comment(c.clone()),
|
Comment(c => es(c).await),
|
||||||
NewExpr(expr => X::from_api(expr, xctx, pos.clone(), i).await),
|
NewExpr(expr => X::from_api(expr, xctx, pos.clone()).await),
|
||||||
Handle(tk => H::from_api(tk, hctx, pos.clone(), i).await)
|
Handle(tk => H::from_api(tk, hctx, pos.clone()).await)
|
||||||
});
|
});
|
||||||
Self { sr: pos, tok }
|
Self { sr: pos, tok }
|
||||||
}
|
}
|
||||||
@@ -135,7 +153,7 @@ impl<H: ExprRepr, X: ExtraTok> TokTree<H, X> {
|
|||||||
BR,
|
BR,
|
||||||
NS(n.to_api(), b => Box::new(b.into_api(hctx, xctx).boxed_local().await)),
|
NS(n.to_api(), b => Box::new(b.into_api(hctx, xctx).boxed_local().await)),
|
||||||
Bottom(e.to_api()),
|
Bottom(e.to_api()),
|
||||||
Comment(c.clone()),
|
Comment(c.to_api()),
|
||||||
LambdaHead(arg => Box::new(arg.into_api(hctx, xctx).boxed_local().await)),
|
LambdaHead(arg => Box::new(arg.into_api(hctx, xctx).boxed_local().await)),
|
||||||
Name(nn.to_api()),
|
Name(nn.to_api()),
|
||||||
S(p, b => ttv_into_api(b, hctx, xctx).boxed_local().await),
|
S(p, b => ttv_into_api(b, hctx, xctx).boxed_local().await),
|
||||||
@@ -145,8 +163,8 @@ impl<H: ExprRepr, X: ExtraTok> TokTree<H, X> {
|
|||||||
api::TokenTree { range: self.sr.range.clone(), token }
|
api::TokenTree { range: self.sr.range.clone(), token }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_kw(&self, tk: Tok<String>) -> bool { self.tok.is_kw(tk) }
|
pub fn is_kw(&self, tk: IStr) -> bool { self.tok.is_kw(tk) }
|
||||||
pub fn as_name(&self) -> Option<Tok<String>> {
|
pub fn as_name(&self) -> Option<IStr> {
|
||||||
if let Token::Name(n) = &self.tok { Some(n.clone()) } else { None }
|
if let Token::Name(n) = &self.tok { Some(n.clone()) } else { None }
|
||||||
}
|
}
|
||||||
pub fn as_multiname(&self) -> Result<VName, &TokTree<H, X>> {
|
pub fn as_multiname(&self) -> Result<VName, &TokTree<H, X>> {
|
||||||
@@ -188,22 +206,22 @@ impl<H: ExprRepr, X: ExtraTok> Format for TokTree<H, X> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Receive a token sequence from API
|
||||||
pub async fn ttv_from_api<H: ExprRepr, X: ExtraTok>(
|
pub async fn ttv_from_api<H: ExprRepr, X: ExtraTok>(
|
||||||
tokv: impl IntoIterator<Item: Borrow<api::TokenTree>>,
|
tokv: impl IntoIterator<Item = api::TokenTree>,
|
||||||
hctx: &mut H::FromApiCtx<'_>,
|
hctx: &mut H::FromApiCtx<'_>,
|
||||||
xctx: &mut X::FromApiCtx<'_>,
|
xctx: &mut X::FromApiCtx<'_>,
|
||||||
src: &Sym,
|
src: &Sym,
|
||||||
i: &Interner,
|
|
||||||
) -> Vec<TokTree<H, X>> {
|
) -> Vec<TokTree<H, X>> {
|
||||||
stream(async |mut cx| {
|
stream(async |mut cx| {
|
||||||
for tok in tokv {
|
for tok in tokv {
|
||||||
cx.emit(TokTree::<H, X>::from_api(tok.borrow(), hctx, xctx, src, i).boxed_local().await).await
|
cx.emit(TokTree::<H, X>::from_api(tok, hctx, xctx, src).boxed_local().await).await
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
/// Encode a token sequence for sending
|
||||||
pub async fn ttv_into_api<H: ExprRepr, X: ExtraTok>(
|
pub async fn ttv_into_api<H: ExprRepr, X: ExtraTok>(
|
||||||
tokv: impl IntoIterator<Item = TokTree<H, X>>,
|
tokv: impl IntoIterator<Item = TokTree<H, X>>,
|
||||||
hctx: &mut H::ToApiCtx<'_>,
|
hctx: &mut H::ToApiCtx<'_>,
|
||||||
@@ -218,6 +236,7 @@ pub async fn ttv_into_api<H: ExprRepr, X: ExtraTok>(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enclose the tokens in `()` if there is more than one
|
||||||
pub fn wrap_tokv<H: ExprRepr, X: ExtraTok>(
|
pub fn wrap_tokv<H: ExprRepr, X: ExtraTok>(
|
||||||
items: impl IntoIterator<Item = TokTree<H, X>>,
|
items: impl IntoIterator<Item = TokTree<H, X>>,
|
||||||
) -> TokTree<H, X> {
|
) -> TokTree<H, X> {
|
||||||
@@ -232,22 +251,20 @@ pub fn wrap_tokv<H: ExprRepr, X: ExtraTok>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use api::Paren;
|
|
||||||
|
|
||||||
/// Lexer output variant
|
/// Lexer output variant
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Token<H: ExprRepr, X: ExtraTok> {
|
pub enum Token<H: ExprRepr, X: ExtraTok> {
|
||||||
/// Information about the code addressed to the human reader or dev tooling
|
/// Information about the code addressed to the human reader or dev tooling
|
||||||
/// It has no effect on the behaviour of the program unless it's explicitly
|
/// It has no effect on the behaviour of the program unless it's explicitly
|
||||||
/// read via reflection
|
/// read via reflection
|
||||||
Comment(Rc<String>),
|
Comment(IStr),
|
||||||
/// The part of a lambda between `\` and `.` enclosing the argument. The body
|
/// The part of a lambda between `\` and `.` enclosing the argument. The body
|
||||||
/// stretches to the end of the enclosing parens or the end of the const line
|
/// stretches to the end of the enclosing parens or the end of the const line
|
||||||
LambdaHead(Box<TokTree<H, X>>),
|
LambdaHead(Box<TokTree<H, X>>),
|
||||||
/// A binding, operator, or a segment of a namespaced::name
|
/// A binding, operator, or a segment of a namespaced::name
|
||||||
Name(Tok<String>),
|
Name(IStr),
|
||||||
/// A namespace prefix, like `my_ns::` followed by a token
|
/// A namespace prefix, like `my_ns::` followed by a token
|
||||||
NS(Tok<String>, Box<TokTree<H, X>>),
|
NS(IStr, Box<TokTree<H, X>>),
|
||||||
/// A line break
|
/// A line break
|
||||||
BR,
|
BR,
|
||||||
/// `()`, `[]`, or `{}`
|
/// `()`, `[]`, or `{}`
|
||||||
@@ -263,7 +280,7 @@ pub enum Token<H: ExprRepr, X: ExtraTok> {
|
|||||||
}
|
}
|
||||||
impl<H: ExprRepr, X: ExtraTok> Token<H, X> {
|
impl<H: ExprRepr, X: ExtraTok> Token<H, X> {
|
||||||
pub fn at(self, sr: SrcRange) -> TokTree<H, X> { TokTree { sr, tok: self } }
|
pub fn at(self, sr: SrcRange) -> TokTree<H, X> { TokTree { sr, tok: self } }
|
||||||
pub fn is_kw(&self, tk: Tok<String>) -> bool { matches!(self, Token::Name(n) if *n == tk) }
|
pub fn is_kw(&self, tk: IStr) -> bool { matches!(self, Token::Name(n) if *n == tk) }
|
||||||
pub fn as_s(&self, par: Paren) -> Option<&[TokTree<H, X>]> {
|
pub fn as_s(&self, par: Paren) -> Option<&[TokTree<H, X>]> {
|
||||||
match self {
|
match self {
|
||||||
Self::S(p, b) if *p == par => Some(b),
|
Self::S(p, b) if *p == par => Some(b),
|
||||||
@@ -275,8 +292,10 @@ impl<H: ExprRepr, X: ExtraTok> Format for Token<H, X> {
|
|||||||
async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||||
match self {
|
match self {
|
||||||
Self::BR => "\n".to_string().into(),
|
Self::BR => "\n".to_string().into(),
|
||||||
Self::Bottom(err) if err.len() == 1 => format!("Bottom({}) ", err.one().unwrap()).into(),
|
Self::Bottom(err) => match err.one() {
|
||||||
Self::Bottom(err) => format!("Botttom(\n{}) ", indent(&err.to_string())).into(),
|
Some(err) => format!("Bottom({err}) ").into(),
|
||||||
|
None => format!("Botttom(\n{}) ", indent(&err.to_string())).into(),
|
||||||
|
},
|
||||||
Self::Comment(c) => format!("--[{c}]--").into(),
|
Self::Comment(c) => format!("--[{c}]--").into(),
|
||||||
Self::LambdaHead(arg) =>
|
Self::LambdaHead(arg) =>
|
||||||
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("\\{0b}.")))
|
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("\\{0b}.")))
|
||||||
@@ -298,16 +317,27 @@ impl<H: ExprRepr, X: ExtraTok> Format for Token<H, X> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find the location that best describes a sequence of tokens if the sequence
|
||||||
|
/// isn't empty
|
||||||
pub fn ttv_range<'a>(ttv: &[TokTree<impl ExprRepr + 'a, impl ExtraTok + 'a>]) -> Option<SrcRange> {
|
pub fn ttv_range<'a>(ttv: &[TokTree<impl ExprRepr + 'a, impl ExtraTok + 'a>]) -> Option<SrcRange> {
|
||||||
let range = ttv.first()?.sr.range.start..ttv.last().unwrap().sr.range.end;
|
let range = ttv.first()?.sr.range.start..ttv.last().unwrap().sr.range.end;
|
||||||
Some(SrcRange { path: ttv.first().unwrap().sr.path(), range })
|
Some(SrcRange { path: ttv.first().unwrap().sr.path(), range })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pretty-print a token sequence
|
||||||
pub async fn ttv_fmt<'a: 'b, 'b>(
|
pub async fn ttv_fmt<'a: 'b, 'b>(
|
||||||
ttv: impl IntoIterator<Item = &'b TokTree<impl ExprRepr + 'a, impl ExtraTok + 'a>>,
|
ttv: impl IntoIterator<Item = &'b TokTree<impl ExprRepr + 'a, impl ExtraTok + 'a>>,
|
||||||
c: &(impl FmtCtx + ?Sized),
|
c: &(impl FmtCtx + ?Sized),
|
||||||
) -> FmtUnit {
|
) -> FmtUnit {
|
||||||
FmtUnit::sequence("", " ", "", None, join_all(ttv.into_iter().map(|t| t.print(c))).await)
|
FmtUnit::sequence("", " ", "", true, join_all(ttv.into_iter().map(|t| t.print(c))).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FmtTTV<'a, H: ExprRepr, X: ExtraTok>(pub &'a [TokTree<H, X>]);
|
||||||
|
impl<'b, H: ExprRepr, X: ExtraTok> Format for FmtTTV<'b, H, X> {
|
||||||
|
async fn print<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||||
|
ttv_fmt(self.0, c).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indent a string by two spaces
|
||||||
pub fn indent(s: &str) -> String { s.replace("\n", "\n ") }
|
pub fn indent(s: &str) -> String { s.replace("\n", "\n ") }
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ pub enum Loaded {
|
|||||||
Code(Arc<String>),
|
Code(Arc<String>),
|
||||||
/// Conceptually equivalent to the list of *.orc files in a folder, without
|
/// Conceptually equivalent to the list of *.orc files in a folder, without
|
||||||
/// the extension
|
/// the extension
|
||||||
Collection(Arc<Vec<Tok<String>>>),
|
Collection(Arc<Vec<IStr>>),
|
||||||
}
|
}
|
||||||
impl Loaded {
|
impl Loaded {
|
||||||
/// Is the loaded item source code (not a collection)?
|
/// Is the loaded item source code (not a collection)?
|
||||||
pub fn is_code(&self) -> bool { matches!(self, Loaded::Code(_)) }
|
pub fn is_code(&self) -> bool { matches!(self, Loaded::Code(_)) }
|
||||||
/// Collect the elements in a collection rreport
|
/// Collect the elements in a collection rreport
|
||||||
pub fn collection(items: impl IntoIterator<Item = Tok<String>>) -> Self {
|
pub fn collection(items: impl IntoIterator<Item = IStr>) -> Self {
|
||||||
Self::Collection(Arc::new(items.into_iter().collect()))
|
Self::Collection(Arc::new(items.into_iter().collect()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ impl ErrorSansOrigin for CodeNotFound {
|
|||||||
/// formats and other sources for libraries and dependencies.
|
/// formats and other sources for libraries and dependencies.
|
||||||
pub trait VirtFS {
|
pub trait VirtFS {
|
||||||
/// Implementation of [VirtFS::read]
|
/// Implementation of [VirtFS::read]
|
||||||
fn get(&self, path: &[Tok<String>], full_path: &PathSlice) -> FSResult;
|
fn get(&self, path: &[IStr], full_path: &PathSlice) -> FSResult;
|
||||||
/// Discover information about a path without reading it.
|
/// Discover information about a path without reading it.
|
||||||
///
|
///
|
||||||
/// Implement this if your vfs backend can do expensive operations
|
/// Implement this if your vfs backend can do expensive operations
|
||||||
@@ -68,7 +68,7 @@ pub trait VirtFS {
|
|||||||
}
|
}
|
||||||
/// Convert a path into a human-readable string that is meaningful in the
|
/// Convert a path into a human-readable string that is meaningful in the
|
||||||
/// target context.
|
/// target context.
|
||||||
fn display(&self, path: &[Tok<String>]) -> Option<String>;
|
fn display(&self, path: &[IStr]) -> Option<String>;
|
||||||
/// Convert the FS handler into a type-erased version of itself for packing in
|
/// Convert the FS handler into a type-erased version of itself for packing in
|
||||||
/// a tree.
|
/// a tree.
|
||||||
fn rc(self) -> Rc<dyn VirtFS>
|
fn rc(self) -> Rc<dyn VirtFS>
|
||||||
@@ -81,15 +81,11 @@ pub trait VirtFS {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VirtFS for &dyn VirtFS {
|
impl VirtFS for &dyn VirtFS {
|
||||||
fn get(&self, path: &[Tok<String>], full_path: &PathSlice) -> FSResult {
|
fn get(&self, path: &[IStr], full_path: &PathSlice) -> FSResult { (*self).get(path, full_path) }
|
||||||
(*self).get(path, full_path)
|
fn display(&self, path: &[IStr]) -> Option<String> { (*self).display(path) }
|
||||||
}
|
|
||||||
fn display(&self, path: &[Tok<String>]) -> Option<String> { (*self).display(path) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: VirtFS + ?Sized> VirtFS for Rc<T> {
|
impl<T: VirtFS + ?Sized> VirtFS for Rc<T> {
|
||||||
fn get(&self, path: &[Tok<String>], full_path: &PathSlice) -> FSResult {
|
fn get(&self, path: &[IStr], full_path: &PathSlice) -> FSResult { (**self).get(path, full_path) }
|
||||||
(**self).get(path, full_path)
|
fn display(&self, path: &[IStr]) -> Option<String> { (**self).display(path) }
|
||||||
}
|
|
||||||
fn display(&self, path: &[Tok<String>]) -> Option<String> { (**self).display(path) }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ impl<'a> Combine for &'a dyn VirtFS {
|
|||||||
pub type DeclTree = ModEntry<Rc<dyn VirtFS>, (), ()>;
|
pub type DeclTree = ModEntry<Rc<dyn VirtFS>, (), ()>;
|
||||||
|
|
||||||
impl VirtFS for DeclTree {
|
impl VirtFS for DeclTree {
|
||||||
fn get(&self, path: &[Tok<String>], full_path: &PathSlice) -> FSResult {
|
fn get(&self, path: &[IStr], full_path: &PathSlice) -> FSResult {
|
||||||
match &self.member {
|
match &self.member {
|
||||||
ModMember::Item(it) => it.get(path, full_path),
|
ModMember::Item(it) => it.get(path, full_path),
|
||||||
ModMember::Sub(module) => match path.split_first() {
|
ModMember::Sub(module) => match path.split_first() {
|
||||||
@@ -44,7 +44,7 @@ impl VirtFS for DeclTree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display(&self, path: &[Tok<String>]) -> Option<String> {
|
fn display(&self, path: &[IStr]) -> Option<String> {
|
||||||
let (head, tail) = path.split_first()?;
|
let (head, tail) = path.split_first()?;
|
||||||
match &self.member {
|
match &self.member {
|
||||||
ModMember::Item(it) => it.display(path),
|
ModMember::Item(it) => it.display(path),
|
||||||
@@ -54,16 +54,16 @@ impl VirtFS for DeclTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VirtFS for String {
|
impl VirtFS for String {
|
||||||
fn display(&self, _: &[Tok<String>]) -> Option<String> { None }
|
fn display(&self, _: &[IStr]) -> Option<String> { None }
|
||||||
fn get(&self, path: &[Tok<String>], full_path: &PathSlice) -> FSResult {
|
fn get(&self, path: &[IStr], full_path: &PathSlice) -> FSResult {
|
||||||
(path.is_empty().then(|| Loaded::Code(Arc::new(self.as_str().to_string()))))
|
(path.is_empty().then(|| Loaded::Code(Arc::new(self.as_str().to_string()))))
|
||||||
.ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())
|
.ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> VirtFS for &'a str {
|
impl<'a> VirtFS for &'a str {
|
||||||
fn display(&self, _: &[Tok<String>]) -> Option<String> { None }
|
fn display(&self, _: &[IStr]) -> Option<String> { None }
|
||||||
fn get(&self, path: &[Tok<String>], full_path: &PathSlice) -> FSResult {
|
fn get(&self, path: &[IStr], full_path: &PathSlice) -> FSResult {
|
||||||
(path.is_empty().then(|| Loaded::Code(Arc::new(self.to_string()))))
|
(path.is_empty().then(|| Loaded::Code(Arc::new(self.to_string()))))
|
||||||
.ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())
|
.ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,14 +99,14 @@ impl DirNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mk_pathbuf(&self, path: &[Tok<String>]) -> PathBuf {
|
fn mk_pathbuf(&self, path: &[IStr]) -> PathBuf {
|
||||||
let mut fpath = self.root.clone();
|
let mut fpath = self.root.clone();
|
||||||
path.iter().for_each(|seg| fpath.push(seg.as_str()));
|
path.iter().for_each(|seg| fpath.push(seg.as_str()));
|
||||||
fpath
|
fpath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl VirtFS for DirNode {
|
impl VirtFS for DirNode {
|
||||||
fn get(&self, path: &[Tok<String>], full_path: &PathSlice) -> FSResult {
|
fn get(&self, path: &[IStr], full_path: &PathSlice) -> FSResult {
|
||||||
let fpath = self.mk_pathbuf(path);
|
let fpath = self.mk_pathbuf(path);
|
||||||
let mut binding = self.cached.borrow_mut();
|
let mut binding = self.cached.borrow_mut();
|
||||||
let (_, res) = (binding.raw_entry_mut().from_key(&fpath))
|
let (_, res) = (binding.raw_entry_mut().from_key(&fpath))
|
||||||
@@ -114,7 +114,7 @@ impl VirtFS for DirNode {
|
|||||||
res.clone()
|
res.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display(&self, path: &[Tok<String>]) -> Option<String> {
|
fn display(&self, path: &[IStr]) -> Option<String> {
|
||||||
let pathbuf = self.mk_pathbuf(path).with_extension(self.ext());
|
let pathbuf = self.mk_pathbuf(path).with_extension(self.ext());
|
||||||
Some(pathbuf.to_string_lossy().to_string())
|
Some(pathbuf.to_string_lossy().to_string())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ impl EmbeddedFS {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VirtFS for EmbeddedFS {
|
impl VirtFS for EmbeddedFS {
|
||||||
fn get(&self, path: &[Tok<String>], full_path: &PathSlice) -> FSResult {
|
fn get(&self, path: &[IStr], full_path: &PathSlice) -> FSResult {
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
return Ok(Loaded::collection(self.tree.keys(|_| true)));
|
return Ok(Loaded::collection(self.tree.keys(|_| true)));
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ impl VirtFS for EmbeddedFS {
|
|||||||
ModMember::Sub(sub) => Loaded::collection(sub.keys(|_| true)),
|
ModMember::Sub(sub) => Loaded::collection(sub.keys(|_| true)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn display(&self, path: &[Tok<String>]) -> Option<String> {
|
fn display(&self, path: &[IStr]) -> Option<String> {
|
||||||
let Self { gen, suffix, .. } = self;
|
let Self { gen, suffix, .. } = self;
|
||||||
Some(format!("{}{suffix} in {gen}", path.iter().join("/")))
|
Some(format!("{}{suffix} in {gen}", path.iter().join("/")))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,18 +21,18 @@ impl<'a> PrefixFS<'a> {
|
|||||||
add: VPath::parse(add.as_ref()),
|
add: VPath::parse(add.as_ref()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn proc_path(&self, path: &[Tok<String>]) -> Option<Vec<Tok<String>>> {
|
fn proc_path(&self, path: &[IStr]) -> Option<Vec<IStr>> {
|
||||||
let path = path.strip_prefix(self.remove.as_slice())?;
|
let path = path.strip_prefix(self.remove.as_slice())?;
|
||||||
Some(self.add.0.iter().chain(path).cloned().collect_vec())
|
Some(self.add.0.iter().chain(path).cloned().collect_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> VirtFS for PrefixFS<'a> {
|
impl<'a> VirtFS for PrefixFS<'a> {
|
||||||
fn get(&self, path: &[Tok<String>], full_path: &PathSlice) -> super::FSResult {
|
fn get(&self, path: &[IStr], full_path: &PathSlice) -> super::FSResult {
|
||||||
let path =
|
let path =
|
||||||
self.proc_path(path).ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())?;
|
self.proc_path(path).ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())?;
|
||||||
self.wrapped.get(&path, full_path)
|
self.wrapped.get(&path, full_path)
|
||||||
}
|
}
|
||||||
fn display(&self, path: &[Tok<String>]) -> Option<String> {
|
fn display(&self, path: &[IStr]) -> Option<String> {
|
||||||
self.wrapped.display(&self.proc_path(path)?)
|
self.wrapped.display(&self.proc_path(path)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,21 @@ edition = "2024"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-event = "0.2.1"
|
||||||
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
|
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
|
||||||
async-once-cell = "0.5.4"
|
async-once-cell = "0.5.4"
|
||||||
|
chrono = "0.4.44"
|
||||||
derive_destructure = "1.0.0"
|
derive_destructure = "1.0.0"
|
||||||
dyn-clone = "1.0.20"
|
dyn-clone = "1.0.20"
|
||||||
futures = { version = "0.3.31", features = [
|
futures = { version = "0.3.31", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"async-await",
|
"async-await",
|
||||||
], default-features = false }
|
] }
|
||||||
futures-locks = "0.7.1"
|
futures-locks = "0.7.1"
|
||||||
hashbrown = "0.16.0"
|
hashbrown = "0.16.1"
|
||||||
include_dir = { version = "0.7.4", optional = true }
|
include_dir = { version = "0.7.4", optional = true }
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
konst = "0.4.2"
|
konst = "0.4.3"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
memo-map = "0.3.3"
|
memo-map = "0.3.3"
|
||||||
never = "0.1.0"
|
never = "0.1.0"
|
||||||
@@ -26,16 +28,17 @@ once_cell = "1.21.3"
|
|||||||
orchid-api = { version = "0.1.0", path = "../orchid-api" }
|
orchid-api = { version = "0.1.0", path = "../orchid-api" }
|
||||||
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
|
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
|
||||||
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
|
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
|
||||||
|
orchid-async-utils = { version = "0.1.0", path = "../orchid-async-utils" }
|
||||||
orchid-base = { version = "0.1.0", path = "../orchid-base" }
|
orchid-base = { version = "0.1.0", path = "../orchid-base" }
|
||||||
ordered-float = "5.0.0"
|
ordered-float = "5.1.0"
|
||||||
pastey = "0.1.1"
|
pastey = "0.2.1"
|
||||||
some_executor = "0.6.1"
|
|
||||||
substack = "1.1.1"
|
substack = "1.1.1"
|
||||||
task-local = "0.1.0"
|
task-local = "0.1.0"
|
||||||
tokio = { version = "1.47.1", optional = true, features = [] }
|
tokio = { version = "1.49.0", optional = true, features = [] }
|
||||||
tokio-util = { version = "0.7.16", optional = true, features = ["compat"] }
|
tokio-util = { version = "0.7.17", optional = true, features = ["compat"] }
|
||||||
|
|
||||||
trait-set = "0.3.0"
|
trait-set = "0.3.0"
|
||||||
|
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tokio = ["dep:tokio", "dep:tokio-util"]
|
tokio = ["dep:tokio", "dep:tokio-util"]
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::any::{Any, TypeId, type_name};
|
use std::any::{Any, TypeId, type_name};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt::{self, Debug};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::io;
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
@@ -9,79 +11,71 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use dyn_clone::{DynClone, clone_box};
|
use dyn_clone::{DynClone, clone_box};
|
||||||
use futures::future::LocalBoxFuture;
|
use futures::future::LocalBoxFuture;
|
||||||
use futures::{AsyncRead, AsyncWrite, FutureExt, StreamExt, stream};
|
use futures::{AsyncWrite, FutureExt, StreamExt, stream};
|
||||||
use orchid_api_derive::Coding;
|
use orchid_api_derive::Coding;
|
||||||
use orchid_api_traits::{Coding, Decode, Encode, Request, enc_vec};
|
use orchid_api_traits::{Coding, Decode, InHierarchy, Request, UnderRoot, enc_vec};
|
||||||
use orchid_base::error::{OrcErrv, OrcRes, mk_errv, mk_errv_floating};
|
use orchid_base::{
|
||||||
use orchid_base::format::{FmtCtx, FmtUnit, Format, fmt};
|
FmtCtx, FmtUnit, Format, IStr, OrcErrv, Pos, Receipt, ReqHandle, ReqReader, ReqReaderExt, Sym,
|
||||||
use orchid_base::location::Pos;
|
fmt, is, mk_errv, mk_errv_floating, take_first,
|
||||||
use orchid_base::name::Sym;
|
};
|
||||||
use orchid_base::reqnot::Requester;
|
|
||||||
use trait_set::trait_set;
|
use trait_set::trait_set;
|
||||||
|
|
||||||
use crate::api;
|
|
||||||
use crate::context::{ctx, i};
|
|
||||||
use crate::conv::ToExpr;
|
|
||||||
// use crate::error::{ProjectError, ProjectResult};
|
|
||||||
use crate::expr::{Expr, ExprData, ExprHandle, ExprKind};
|
|
||||||
use crate::gen_expr::GExpr;
|
use crate::gen_expr::GExpr;
|
||||||
use crate::system::{DynSystemCard, atom_info_for, downcast_atom};
|
use crate::{
|
||||||
|
DynSystemCardExt, Expr, ExprData, ExprHandle, ExprKind, OwnedAtom, ToExpr, api, dyn_cted,
|
||||||
|
get_obj_store, request, sys_id,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)]
|
/// Every atom managed via this system starts with an ID into the type table
|
||||||
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)]
|
||||||
pub struct AtomTypeId(pub NonZeroU32);
|
pub struct AtomTypeId(pub NonZeroU32);
|
||||||
|
|
||||||
pub trait AtomCard: 'static + Sized {
|
|
||||||
type Data: Clone + Coding + Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AtomicVariant {}
|
pub trait AtomicVariant {}
|
||||||
|
|
||||||
|
/// A value managed by Orchid. The type should also be registered in the
|
||||||
|
/// [crate::SystemCard] through [AtomicFeatures::ops] which is provided
|
||||||
|
/// indirectly by either [crate::OwnedAtom] or [crate::ThinAtom]
|
||||||
pub trait Atomic: 'static + Sized {
|
pub trait Atomic: 'static + Sized {
|
||||||
|
/// Either [crate::OwnedVariant] or [crate::ThinVariant] depending on whether
|
||||||
|
/// the value implements [crate::OwnedAtom] or [crate::ThinAtom]
|
||||||
type Variant: AtomicVariant;
|
type Variant: AtomicVariant;
|
||||||
|
/// Serializable data that gets sent inside the atom to other systems that
|
||||||
|
/// depend on this system. Methods on this value are directly accessible
|
||||||
|
/// through [TAtom], and this data can also be used for optimized public
|
||||||
|
/// functions. The serialized form should have a reasonable length to avoid
|
||||||
|
/// overburdening the protocol.
|
||||||
type Data: Clone + Coding + Sized + 'static;
|
type Data: Clone + Coding + Sized + 'static;
|
||||||
/// Register handlers for IPC calls. If this atom implements [Supports], you
|
/// Register handlers for IPC calls. If this atom implements [Supports], you
|
||||||
/// should register your implementations here. If this atom doesn't
|
/// should register your implementations here. If this atom doesn't
|
||||||
/// participate in IPC at all, the default implementation is fine
|
/// participate in IPC at all, the default implementation is fine
|
||||||
fn reg_reqs() -> MethodSetBuilder<Self> { MethodSetBuilder::new() }
|
fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new() }
|
||||||
}
|
|
||||||
impl<A: Atomic> AtomCard for A {
|
|
||||||
type Data = <Self as Atomic>::Data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shared interface of all atom types created in this library for use by the
|
||||||
|
/// library that defines them. This is provided by [Atomic] and either
|
||||||
|
/// [crate::OwnedAtom] or [crate::ThinAtom]
|
||||||
pub trait AtomicFeatures: Atomic {
|
pub trait AtomicFeatures: Atomic {
|
||||||
|
/// Convert a value of this atom inside the defining system into a function
|
||||||
|
/// that will perform registrations and serialization
|
||||||
|
#[allow(private_interfaces)]
|
||||||
fn factory(self) -> AtomFactory;
|
fn factory(self) -> AtomFactory;
|
||||||
type Info: AtomDynfo;
|
/// Expose all operations that can be performed on an instance of this type in
|
||||||
fn info() -> Self::Info;
|
/// an instanceless vtable. This vtable must be registered by the
|
||||||
fn dynfo() -> Box<dyn AtomDynfo>;
|
/// [crate::System].
|
||||||
|
fn ops() -> Box<dyn AtomOps>;
|
||||||
}
|
}
|
||||||
pub trait ToAtom {
|
pub(crate) trait AtomicFeaturesImpl<Variant: AtomicVariant> {
|
||||||
fn to_atom_factory(self) -> AtomFactory;
|
|
||||||
}
|
|
||||||
impl<A: AtomicFeatures> ToAtom for A {
|
|
||||||
fn to_atom_factory(self) -> AtomFactory { self.factory() }
|
|
||||||
}
|
|
||||||
impl ToAtom for AtomFactory {
|
|
||||||
fn to_atom_factory(self) -> AtomFactory { self }
|
|
||||||
}
|
|
||||||
pub trait AtomicFeaturesImpl<Variant: AtomicVariant> {
|
|
||||||
fn _factory(self) -> AtomFactory;
|
fn _factory(self) -> AtomFactory;
|
||||||
type _Info: AtomDynfo;
|
type _Info: AtomOps;
|
||||||
fn _info() -> Self::_Info;
|
fn _info() -> Self::_Info;
|
||||||
}
|
}
|
||||||
impl<A: Atomic + AtomicFeaturesImpl<A::Variant>> AtomicFeatures for A {
|
impl<A: Atomic + AtomicFeaturesImpl<A::Variant>> AtomicFeatures for A {
|
||||||
|
#[allow(private_interfaces)]
|
||||||
fn factory(self) -> AtomFactory { self._factory() }
|
fn factory(self) -> AtomFactory { self._factory() }
|
||||||
type Info = <Self as AtomicFeaturesImpl<A::Variant>>::_Info;
|
fn ops() -> Box<dyn AtomOps> { Box::new(Self::_info()) }
|
||||||
fn info() -> Self::Info { Self::_info() }
|
|
||||||
fn dynfo() -> Box<dyn AtomDynfo> { Box::new(Self::info()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_info<A: AtomCard>(
|
|
||||||
sys: &(impl DynSystemCard + ?Sized),
|
|
||||||
) -> (AtomTypeId, Box<dyn AtomDynfo>) {
|
|
||||||
atom_info_for(sys, TypeId::of::<A>()).unwrap_or_else(|| {
|
|
||||||
panic!("Atom {} not associated with system {}", type_name::<A>(), sys.name())
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A reference to a value of some [Atomic] type. This owns an [Expr]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ForeignAtom {
|
pub struct ForeignAtom {
|
||||||
pub(crate) expr: Rc<ExprHandle>,
|
pub(crate) expr: Rc<ExprHandle>,
|
||||||
@@ -89,7 +83,9 @@ pub struct ForeignAtom {
|
|||||||
pub(crate) pos: Pos,
|
pub(crate) pos: Pos,
|
||||||
}
|
}
|
||||||
impl ForeignAtom {
|
impl ForeignAtom {
|
||||||
|
/// Obtain the position in code of the expression
|
||||||
pub fn pos(&self) -> Pos { self.pos.clone() }
|
pub fn pos(&self) -> Pos { self.pos.clone() }
|
||||||
|
/// Obtain the [Expr]
|
||||||
pub fn ex(self) -> Expr {
|
pub fn ex(self) -> Expr {
|
||||||
let (handle, pos) = (self.expr.clone(), self.pos.clone());
|
let (handle, pos) = (self.expr.clone(), self.pos.clone());
|
||||||
let data = ExprData { pos, kind: ExprKind::Atom(ForeignAtom { ..self }) };
|
let data = ExprData { pos, kind: ExprKind::Atom(ForeignAtom { ..self }) };
|
||||||
@@ -98,17 +94,40 @@ impl ForeignAtom {
|
|||||||
pub(crate) fn new(handle: Rc<ExprHandle>, atom: api::Atom, pos: Pos) -> Self {
|
pub(crate) fn new(handle: Rc<ExprHandle>, atom: api::Atom, pos: Pos) -> Self {
|
||||||
ForeignAtom { atom, expr: handle, pos }
|
ForeignAtom { atom, expr: handle, pos }
|
||||||
}
|
}
|
||||||
pub async fn request<M: AtomMethod>(&self, m: M) -> Option<M::Response> {
|
/// Call an IPC method. If the type does not support the given method type,
|
||||||
let rep = (ctx().reqnot().request(api::Fwd(
|
/// this function returns [None]
|
||||||
self.atom.clone(),
|
pub async fn call<R: Request + UnderRoot<Root: AtomMethod>>(&self, r: R) -> Option<R::Response> {
|
||||||
Sym::parse(M::NAME, &i()).await.unwrap().tok().to_api(),
|
let rep = (request(api::Fwd {
|
||||||
enc_vec(&m).await,
|
target: self.atom.clone(),
|
||||||
)))
|
method: Sym::parse(<R as UnderRoot>::Root::NAME).await.unwrap().tok().to_api(),
|
||||||
|
body: enc_vec(&r.into_root()),
|
||||||
|
}))
|
||||||
.await?;
|
.await?;
|
||||||
Some(M::Response::decode(Pin::new(&mut &rep[..])).await)
|
Some(R::Response::decode_slice(&mut &rep[..]))
|
||||||
}
|
}
|
||||||
pub async fn downcast<T: AtomicFeatures>(self) -> Result<TAtom<T>, NotTypAtom> {
|
/// Attempt to downcast this value to a concrete atom type
|
||||||
TAtom::downcast(self.ex().handle()).await
|
pub fn downcast<A: Atomic>(self) -> Result<TAtom<A>, NotTypAtom> {
|
||||||
|
let mut data = &self.atom.data.0[..];
|
||||||
|
let value = AtomTypeId::decode_slice(&mut data);
|
||||||
|
let cted = dyn_cted();
|
||||||
|
let own_inst = cted.inst();
|
||||||
|
let owner_id = self.atom.owner;
|
||||||
|
let typ = type_name::<A>();
|
||||||
|
let owner = if sys_id() == owner_id {
|
||||||
|
own_inst.card()
|
||||||
|
} else {
|
||||||
|
(cted.deps().find(|s| s.id() == self.atom.owner))
|
||||||
|
.ok_or_else(|| NotTypAtom { expr: self.clone().ex(), pos: self.pos(), typ })?
|
||||||
|
.get_card()
|
||||||
|
};
|
||||||
|
let Some(ops) = owner.ops_by_atid(value) else {
|
||||||
|
panic!("{value:?} does not refer to an atom in {owner_id:?} when downcasting {typ}");
|
||||||
|
};
|
||||||
|
if ops.tid() != TypeId::of::<A>() {
|
||||||
|
return Err(NotTypAtom { pos: self.pos.clone(), expr: self.ex(), typ });
|
||||||
|
}
|
||||||
|
let value = A::Data::decode_slice(&mut data);
|
||||||
|
Ok(TAtom { value, untyped: self })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl fmt::Display for ForeignAtom {
|
impl fmt::Display for ForeignAtom {
|
||||||
@@ -119,127 +138,177 @@ impl fmt::Debug for ForeignAtom {
|
|||||||
}
|
}
|
||||||
impl Format for ForeignAtom {
|
impl Format for ForeignAtom {
|
||||||
async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||||
FmtUnit::from_api(&ctx().reqnot().request(api::ExtAtomPrint(self.atom.clone())).await)
|
FmtUnit::from_api(&request(api::ExtAtomPrint(self.atom.clone())).await)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ToExpr for ForeignAtom {
|
impl ToExpr for ForeignAtom {
|
||||||
|
async fn to_expr(self) -> Expr
|
||||||
|
where Self: Sized {
|
||||||
|
self.ex()
|
||||||
|
}
|
||||||
async fn to_gen(self) -> GExpr { self.ex().to_gen().await }
|
async fn to_gen(self) -> GExpr { self.ex().to_gen().await }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NotTypAtom {
|
pub struct NotTypAtom {
|
||||||
pub pos: Pos,
|
pub pos: Pos,
|
||||||
pub expr: Expr,
|
pub expr: Expr,
|
||||||
pub typ: Box<dyn AtomDynfo>,
|
pub typ: &'static str,
|
||||||
}
|
}
|
||||||
impl NotTypAtom {
|
impl NotTypAtom {
|
||||||
|
/// Convert to a generic Orchid error
|
||||||
pub async fn mk_err(&self) -> OrcErrv {
|
pub async fn mk_err(&self) -> OrcErrv {
|
||||||
mk_errv(
|
mk_errv(
|
||||||
i().i("Not the expected type").await,
|
is("Not the expected type").await,
|
||||||
format!("The expression {} is not a {}", fmt(&self.expr, &i()).await, self.typ.name()),
|
format!("The expression {} is not a {}", fmt(&self.expr).await, self.typ),
|
||||||
[self.pos.clone()],
|
[self.pos.clone()],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Debug for NotTypAtom {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("NotTypAtom")
|
||||||
|
.field("pos", &self.pos)
|
||||||
|
.field("expr", &self.expr)
|
||||||
|
.field("typ", &self.typ)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait AtomMethod: Request + Coding {
|
/// An IPC request associated with an atom. This type should either implement
|
||||||
|
/// [Request] or be the root of a [orchid_api_derive::Hierarchy] the leaves of
|
||||||
|
/// which implement [Request].
|
||||||
|
pub trait AtomMethod: Coding + InHierarchy {
|
||||||
const NAME: &str;
|
const NAME: &str;
|
||||||
}
|
}
|
||||||
pub trait Supports<M: AtomMethod>: AtomCard {
|
|
||||||
fn handle(&self, req: M) -> impl Future<Output = <M as Request>::Response>;
|
/// A handler for an [AtomMethod] on an [Atomic]. The [AtomMethod] must also be
|
||||||
|
/// registered in [Atomic::reg_methods]
|
||||||
|
pub trait Supports<M: AtomMethod>: Atomic {
|
||||||
|
fn handle<'a>(
|
||||||
|
&self,
|
||||||
|
hand: Box<dyn ReqHandle<'a> + '_>,
|
||||||
|
req: M,
|
||||||
|
) -> impl Future<Output = io::Result<Receipt<'a>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
trait_set! {
|
trait HandleAtomMethod<A> {
|
||||||
trait AtomReqCb<A> = for<'a> Fn(
|
fn handle<'a, 'b: 'a>(
|
||||||
&'a A,
|
&'a self,
|
||||||
Pin<&'a mut dyn AsyncRead>,
|
atom: &'a A,
|
||||||
Pin<&'a mut dyn AsyncWrite>,
|
reader: Box<dyn ReqReader<'b> + 'a>,
|
||||||
) -> LocalBoxFuture<'a, ()>
|
) -> LocalBoxFuture<'a, ()>;
|
||||||
|
}
|
||||||
|
struct AtomMethodHandler<M, A>(PhantomData<M>, PhantomData<A>);
|
||||||
|
impl<M: AtomMethod, A: Supports<M>> HandleAtomMethod<A> for AtomMethodHandler<M, A> {
|
||||||
|
fn handle<'a, 'b: 'a>(
|
||||||
|
&'a self,
|
||||||
|
atom: &'a A,
|
||||||
|
mut reader: Box<dyn ReqReader<'b> + 'a>,
|
||||||
|
) -> LocalBoxFuture<'a, ()> {
|
||||||
|
Box::pin(async {
|
||||||
|
let req = reader.read_req::<M>().await.unwrap();
|
||||||
|
let _ = Supports::<M>::handle(atom, reader.finish().await, req).await.unwrap();
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MethodSetBuilder<A: AtomCard> {
|
/// A collection of [Supports] impls for an [Atomic]. If a [Supports]
|
||||||
handlers: Vec<(&'static str, Rc<dyn AtomReqCb<A>>)>,
|
/// impl is not added to the method set, it will not be recognized. Note that
|
||||||
|
/// the [Supports] implementors must be registered, which are not necessarily
|
||||||
|
/// the same as the [Request] implementors
|
||||||
|
pub struct MethodSetBuilder<A: Atomic> {
|
||||||
|
handlers: Vec<(&'static str, Rc<dyn HandleAtomMethod<A>>)>,
|
||||||
}
|
}
|
||||||
impl<A: AtomCard> MethodSetBuilder<A> {
|
impl<A: Atomic> MethodSetBuilder<A> {
|
||||||
pub fn new() -> Self { Self { handlers: vec![] } }
|
pub fn new() -> Self { Self { handlers: vec![] } }
|
||||||
|
|
||||||
|
/// Add an [AtomMethod]
|
||||||
pub fn handle<M: AtomMethod>(mut self) -> Self
|
pub fn handle<M: AtomMethod>(mut self) -> Self
|
||||||
where A: Supports<M> {
|
where A: Supports<M> {
|
||||||
assert!(!M::NAME.is_empty(), "AtomMethod::NAME cannoot be empty");
|
assert!(!M::NAME.is_empty(), "AtomMethod::NAME cannoot be empty");
|
||||||
self.handlers.push((
|
self.handlers.push((M::NAME, Rc::new(AtomMethodHandler::<M, A>(PhantomData, PhantomData))));
|
||||||
M::NAME,
|
|
||||||
Rc::new(move |a: &A, req: Pin<&mut dyn AsyncRead>, rep: Pin<&mut dyn AsyncWrite>| {
|
|
||||||
async { Supports::<M>::handle(a, M::decode(req).await).await.encode(rep).await }
|
|
||||||
.boxed_local()
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn pack(&self) -> MethodSet<A> {
|
pub(crate) async fn pack(&self) -> MethodSet<A> {
|
||||||
MethodSet {
|
MethodSet {
|
||||||
handlers: stream::iter(self.handlers.iter())
|
handlers: stream::iter(self.handlers.iter())
|
||||||
.then(async |(k, v)| (Sym::parse(k, &i()).await.unwrap(), v.clone()))
|
.then(async |(k, v)| (Sym::parse(k).await.unwrap(), v.clone()))
|
||||||
.collect()
|
.collect()
|
||||||
.await,
|
.await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MethodSet<A: AtomCard> {
|
pub(crate) struct MethodSet<A: Atomic> {
|
||||||
handlers: HashMap<Sym, Rc<dyn AtomReqCb<A>>>,
|
handlers: HashMap<Sym, Rc<dyn HandleAtomMethod<A>>>,
|
||||||
}
|
}
|
||||||
impl<A: AtomCard> MethodSet<A> {
|
impl<A: Atomic> MethodSet<A> {
|
||||||
pub(crate) async fn dispatch<'a>(
|
pub(crate) async fn dispatch<'a>(
|
||||||
&'a self,
|
&self,
|
||||||
atom: &'a A,
|
atom: &'_ A,
|
||||||
key: Sym,
|
key: Sym,
|
||||||
req: Pin<&'a mut dyn AsyncRead>,
|
req: Box<dyn ReqReader<'a> + 'a>,
|
||||||
rep: Pin<&'a mut dyn AsyncWrite>,
|
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match self.handlers.get(&key) {
|
match self.handlers.get(&key) {
|
||||||
None => false,
|
None => false,
|
||||||
Some(handler) => {
|
Some(handler) => {
|
||||||
handler(atom, req, rep).await;
|
handler.handle(atom, req).await;
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: AtomCard> Default for MethodSetBuilder<A> {
|
impl<A: Atomic> Default for MethodSetBuilder<A> {
|
||||||
fn default() -> Self { Self::new() }
|
fn default() -> Self { Self::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A handle to a value defined by this or another system. This owns an [Expr]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TAtom<A: AtomicFeatures> {
|
pub struct TAtom<A: Atomic> {
|
||||||
pub untyped: ForeignAtom,
|
pub untyped: ForeignAtom,
|
||||||
pub value: A::Data,
|
pub value: A::Data,
|
||||||
}
|
}
|
||||||
impl<A: AtomicFeatures> TAtom<A> {
|
impl<A: Atomic> TAtom<A> {
|
||||||
|
/// Obtain the underlying [Expr]
|
||||||
pub fn ex(&self) -> Expr { self.untyped.clone().ex() }
|
pub fn ex(&self) -> Expr { self.untyped.clone().ex() }
|
||||||
|
/// Obtain the position in code associated with the atom
|
||||||
pub fn pos(&self) -> Pos { self.untyped.pos() }
|
pub fn pos(&self) -> Pos { self.untyped.pos() }
|
||||||
|
/// Produce from an [ExprHandle] directly
|
||||||
pub async fn downcast(expr: Rc<ExprHandle>) -> Result<Self, NotTypAtom> {
|
pub async fn downcast(expr: Rc<ExprHandle>) -> Result<Self, NotTypAtom> {
|
||||||
match Expr::from_handle(expr).atom().await {
|
match Expr::from_handle(expr).atom().await {
|
||||||
Err(expr) =>
|
Err(expr) =>
|
||||||
Err(NotTypAtom { pos: expr.data().await.pos.clone(), expr, typ: Box::new(A::info()) }),
|
Err(NotTypAtom { pos: expr.data().await.pos.clone(), expr, typ: type_name::<A>() }),
|
||||||
Ok(atm) => match downcast_atom::<A>(atm).await {
|
Ok(atm) => atm.downcast(),
|
||||||
Ok(tatom) => Ok(tatom),
|
|
||||||
Err(fa) => Err(NotTypAtom { pos: fa.pos.clone(), expr: fa.ex(), typ: Box::new(A::info()) }),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub async fn request<M: AtomMethod>(&self, req: M) -> M::Response
|
/// Find the instance associated with a [TAtom] that we own
|
||||||
where A: Supports<M> {
|
///
|
||||||
M::Response::decode(Pin::new(
|
/// # Panics
|
||||||
&mut &(ctx().reqnot().request(api::Fwd(
|
///
|
||||||
self.untyped.atom.clone(),
|
/// if we don't actually own this atom
|
||||||
Sym::parse(M::NAME, &i()).await.unwrap().tok().to_api(),
|
pub async fn own(&self) -> A
|
||||||
enc_vec(&req).await,
|
where A: OwnedAtom {
|
||||||
)))
|
let g = get_obj_store().objects.read().await;
|
||||||
|
let atom_id = self.untyped.atom.drop.expect("Owned atoms always have a drop ID");
|
||||||
|
let dyn_atom =
|
||||||
|
g.get(&atom_id).expect("Atom ID invalid; atom type probably not owned by this crate");
|
||||||
|
dyn_atom.as_any_ref().downcast_ref().cloned().expect("The ID should imply a type as well")
|
||||||
|
}
|
||||||
|
/// Call an IPC method on the value. Since we know the type, unlike
|
||||||
|
/// [ForeignAtom::call], we can ensure that the callee recognizes this method
|
||||||
|
pub async fn call<R: Request + UnderRoot<Root: AtomMethod>>(&self, req: R) -> R::Response
|
||||||
|
where A: Supports<<R as UnderRoot>::Root> {
|
||||||
|
R::Response::decode_slice(
|
||||||
|
&mut &(request(api::Fwd {
|
||||||
|
target: self.untyped.atom.clone(),
|
||||||
|
method: Sym::parse(<R as UnderRoot>::Root::NAME).await.unwrap().tok().to_api(),
|
||||||
|
body: enc_vec(&req.into_root()),
|
||||||
|
}))
|
||||||
.await
|
.await
|
||||||
.unwrap()[..],
|
.unwrap()[..],
|
||||||
))
|
)
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<A: AtomicFeatures> Deref for TAtom<A> {
|
impl<A: AtomicFeatures> Deref for TAtom<A> {
|
||||||
@@ -255,61 +324,108 @@ impl<A: AtomicFeatures> Format for TAtom<A> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AtomCtx<'a>(pub &'a [u8], pub Option<api::AtomId>);
|
pub(crate) struct AtomCtx<'a>(pub &'a [u8], pub Option<api::AtomId>);
|
||||||
|
|
||||||
pub trait AtomDynfo: 'static {
|
/// A vtable-like type that collects operations defined by an [Atomic] without
|
||||||
|
/// associating with an instance of that type. This must be registered in
|
||||||
|
/// [crate::SystemCard]
|
||||||
|
#[allow(private_interfaces)]
|
||||||
|
pub trait AtomOps: 'static {
|
||||||
fn tid(&self) -> TypeId;
|
fn tid(&self) -> TypeId;
|
||||||
fn name(&self) -> &'static str;
|
fn name(&self) -> &'static str;
|
||||||
fn decode<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, Box<dyn Any>>;
|
fn decode<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, Box<dyn Any>>;
|
||||||
fn call<'a>(&'a self, ctx: AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr>;
|
fn call<'a>(&'a self, ctx: AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr>;
|
||||||
fn call_ref<'a>(&'a self, ctx: AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr>;
|
fn call_ref<'a>(&'a self, ctx: AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr>;
|
||||||
fn print<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit>;
|
fn print<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit>;
|
||||||
fn handle_req<'a, 'b: 'a, 'c: 'a>(
|
fn handle_req_ref<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
ctx: AtomCtx<'a>,
|
ctx: AtomCtx<'a>,
|
||||||
key: Sym,
|
key: Sym,
|
||||||
req: Pin<&'b mut dyn AsyncRead>,
|
req: Box<dyn ReqReader<'a> + 'a>,
|
||||||
rep: Pin<&'c mut dyn AsyncWrite>,
|
|
||||||
) -> LocalBoxFuture<'a, bool>;
|
) -> LocalBoxFuture<'a, bool>;
|
||||||
fn command<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, OrcRes<Option<GExpr>>>;
|
|
||||||
fn serialize<'a, 'b: 'a>(
|
fn serialize<'a, 'b: 'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
ctx: AtomCtx<'a>,
|
ctx: AtomCtx<'a>,
|
||||||
write: Pin<&'b mut dyn AsyncWrite>,
|
write: Pin<&'b mut dyn AsyncWrite>,
|
||||||
) -> LocalBoxFuture<'a, Option<Vec<Expr>>>;
|
) -> LocalBoxFuture<'a, Option<Vec<Expr>>>;
|
||||||
fn deserialize<'a>(&'a self, data: &'a [u8], refs: &'a [Expr]) -> LocalBoxFuture<'a, api::Atom>;
|
fn deserialize<'a>(
|
||||||
|
&'a self,
|
||||||
|
data: &'a [u8],
|
||||||
|
refs: &'a [Expr],
|
||||||
|
) -> LocalBoxFuture<'a, api::LocalAtom>;
|
||||||
fn drop<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, ()>;
|
fn drop<'a>(&'a self, ctx: AtomCtx<'a>) -> LocalBoxFuture<'a, ()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
trait_set! {
|
trait_set! {
|
||||||
pub trait AtomFactoryFn = FnOnce() -> LocalBoxFuture<'static, api::Atom> + DynClone;
|
pub trait AtomFactoryFn = FnOnce() -> LocalBoxFuture<'static, api::LocalAtom> + DynClone;
|
||||||
}
|
}
|
||||||
pub struct AtomFactory(Box<dyn AtomFactoryFn>);
|
pub(crate) struct AtomFactory(Box<dyn AtomFactoryFn>, String);
|
||||||
impl AtomFactory {
|
impl AtomFactory {
|
||||||
pub fn new(f: impl AsyncFnOnce() -> api::Atom + Clone + 'static) -> Self {
|
pub fn new(name: String, f: impl AsyncFnOnce() -> api::LocalAtom + Clone + 'static) -> Self {
|
||||||
Self(Box::new(|| f().boxed_local()))
|
Self(Box::new(|| f().boxed_local()), name)
|
||||||
}
|
}
|
||||||
pub async fn build(self) -> api::Atom { (self.0)().await }
|
pub async fn build(self) -> api::LocalAtom { (self.0)().await }
|
||||||
}
|
}
|
||||||
impl Clone for AtomFactory {
|
impl Clone for AtomFactory {
|
||||||
fn clone(&self) -> Self { AtomFactory(clone_box(&*self.0)) }
|
fn clone(&self) -> Self { AtomFactory(clone_box(&*self.0), self.1.clone()) }
|
||||||
}
|
}
|
||||||
impl fmt::Debug for AtomFactory {
|
impl fmt::Debug for AtomFactory {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AtomFactory") }
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AtomFactory<{}>", self.1) }
|
||||||
}
|
}
|
||||||
impl fmt::Display for AtomFactory {
|
impl fmt::Display for AtomFactory {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "AtomFactory") }
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self:?}") }
|
||||||
}
|
}
|
||||||
impl Format for AtomFactory {
|
impl Format for AtomFactory {
|
||||||
async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||||
"AtomFactory".to_string().into()
|
self.to_string().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn err_not_callable() -> OrcErrv {
|
/// Error produced when an atom can not be applied to a value as a function
|
||||||
mk_errv_floating(i().i("This atom is not callable").await, "Attempted to apply value as function")
|
pub async fn err_not_callable(unit: &FmtUnit) -> OrcErrv {
|
||||||
|
mk_errv_floating(
|
||||||
|
is("This atom is not callable").await,
|
||||||
|
format!("Attempted to apply {} as function", take_first(unit, false)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn err_not_command() -> OrcErrv {
|
/// Error produced when an atom can not be the final value of the program
|
||||||
mk_errv_floating(i().i("This atom is not a command").await, "Settled on an inactionable value")
|
pub async fn err_not_command(unit: &FmtUnit) -> OrcErrv {
|
||||||
|
mk_errv_floating(
|
||||||
|
is("This atom is not a command").await,
|
||||||
|
format!("Settled on {} which is an inactionable value", take_first(unit, false)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn err_exit_success_msg() -> IStr { is("Early successful exit").await }
|
||||||
|
pub(crate) async fn err_exit_failure_msg() -> IStr { is("Early failure exit").await }
|
||||||
|
|
||||||
|
/// Sentinel error returnable from [crate::OwnedAtom::command] or
|
||||||
|
/// [crate::ThinAtom::command] to indicate that the program should exit with a
|
||||||
|
/// success
|
||||||
|
pub async fn err_exit_success() -> OrcErrv {
|
||||||
|
mk_errv_floating(
|
||||||
|
err_exit_success_msg().await,
|
||||||
|
"Sentinel error indicating that the program should exit with a success.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sentinel error returnable from [crate::OwnedAtom::command] or
|
||||||
|
/// [crate::ThinAtom::command] to indicate that the program should exit with a
|
||||||
|
/// failure
|
||||||
|
pub async fn err_exit_failure() -> OrcErrv {
|
||||||
|
mk_errv_floating(
|
||||||
|
err_exit_failure_msg().await,
|
||||||
|
"Sentinel error indicating that the program should exit with a failure \
|
||||||
|
but without raising an error.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read the type ID prefix from an atom, return type information and the rest
|
||||||
|
/// of the data
|
||||||
|
pub(crate) fn resolve_atom_type(atom: &api::Atom) -> (Box<dyn AtomOps>, AtomTypeId, &[u8]) {
|
||||||
|
let mut data = &atom.data.0[..];
|
||||||
|
let atid = AtomTypeId::decode_slice(&mut data);
|
||||||
|
let atom_record = dyn_cted().inst().card().ops_by_atid(atid).expect("Unrecognized atom type ID");
|
||||||
|
(atom_record, atid, data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,52 @@
|
|||||||
use std::any::{Any, TypeId, type_name};
|
use std::any::{Any, TypeId, type_name};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::num::NonZero;
|
use std::num::NonZero;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::AtomicU64;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use async_once_cell::OnceCell;
|
use async_once_cell::OnceCell;
|
||||||
use dyn_clone::{DynClone, clone_box};
|
use dyn_clone::{DynClone, clone_box};
|
||||||
use futures::future::{LocalBoxFuture, ready};
|
use futures::future::{LocalBoxFuture, ready};
|
||||||
use futures::{AsyncRead, AsyncWrite, FutureExt};
|
use futures::{AsyncWrite, FutureExt};
|
||||||
use futures_locks::{RwLock, RwLockReadGuard};
|
use futures_locks::{RwLock, RwLockReadGuard};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use memo_map::MemoMap;
|
use memo_map::MemoMap;
|
||||||
use never::Never;
|
use never::Never;
|
||||||
use orchid_api_traits::{Decode, Encode, enc_vec};
|
use orchid_api_traits::{Decode, Encode, enc_vec};
|
||||||
use orchid_base::error::OrcRes;
|
use orchid_base::{FmtCtx, FmtCtxImpl, FmtUnit, Format, Sym, log, take_first};
|
||||||
use orchid_base::format::{FmtCtx, FmtCtxImpl, FmtUnit, take_first};
|
use task_local::task_local;
|
||||||
use orchid_base::name::Sym;
|
|
||||||
|
|
||||||
use crate::api;
|
|
||||||
use crate::atom::{
|
|
||||||
AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet,
|
|
||||||
MethodSetBuilder, TAtom, err_not_callable, err_not_command, get_info,
|
|
||||||
};
|
|
||||||
use crate::context::{SysCtxEntry, ctx, i};
|
|
||||||
use crate::expr::Expr;
|
|
||||||
use crate::gen_expr::{GExpr, bot};
|
use crate::gen_expr::{GExpr, bot};
|
||||||
use crate::system_ctor::CtedObj;
|
use crate::{
|
||||||
|
AtomCtx, AtomFactory, AtomOps, Atomic, AtomicFeaturesImpl, AtomicVariant, DynSystemCardExt, Expr,
|
||||||
|
MethodSet, MethodSetBuilder, ToExpr, api, dyn_cted, err_not_callable,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Value of [Atomic::Variant] for a type that implements [OwnedAtom]
|
||||||
pub struct OwnedVariant;
|
pub struct OwnedVariant;
|
||||||
impl AtomicVariant for OwnedVariant {}
|
impl AtomicVariant for OwnedVariant {}
|
||||||
impl<A: OwnedAtom + Atomic<Variant = OwnedVariant>> AtomicFeaturesImpl<OwnedVariant> for A {
|
impl<A: OwnedAtom + Atomic<Variant = OwnedVariant>> AtomicFeaturesImpl<OwnedVariant> for A {
|
||||||
fn _factory(self) -> AtomFactory {
|
fn _factory(self) -> AtomFactory {
|
||||||
AtomFactory::new(async move || {
|
AtomFactory::new(type_name::<A>().to_string(), async move || {
|
||||||
let serial = ctx()
|
let obj_store = get_obj_store();
|
||||||
.get_or_default::<ObjStore>()
|
let atom_id = {
|
||||||
.next_id
|
let mut id = obj_store.next_id.borrow_mut();
|
||||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
*id += 1;
|
||||||
let atom_id = api::AtomId(NonZero::new(serial + 1).unwrap());
|
api::AtomId(NonZero::new(*id + 1).unwrap())
|
||||||
let (typ_id, _) = get_info::<A>(ctx().get::<CtedObj>().inst().card());
|
};
|
||||||
let mut data = enc_vec(&typ_id).await;
|
let (typ_id, _) = dyn_cted().inst().card().ops::<A>();
|
||||||
|
let mut data = enc_vec(&typ_id);
|
||||||
self.encode(Pin::<&mut Vec<u8>>::new(&mut data)).await;
|
self.encode(Pin::<&mut Vec<u8>>::new(&mut data)).await;
|
||||||
ctx().get_or_default::<ObjStore>().objects.read().await.insert(atom_id, Box::new(self));
|
obj_store.objects.read().await.insert(atom_id, Box::new(self));
|
||||||
api::Atom { drop: Some(atom_id), data: api::AtomData(data), owner: ctx().sys_id() }
|
api::LocalAtom { drop: Some(atom_id), data: api::AtomData(data) }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn _info() -> Self::_Info { OwnedAtomDynfo { msbuild: A::reg_reqs(), ms: OnceCell::new() } }
|
fn _info() -> Self::_Info { OwnedAtomOps { msbuild: A::reg_methods(), ms: OnceCell::new() } }
|
||||||
type _Info = OwnedAtomDynfo<A>;
|
type _Info = OwnedAtomOps<A>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// While an atom read guard is held, no atom can be removed.
|
/// While an atom read guard is held, no atom can be removed.
|
||||||
@@ -59,7 +57,7 @@ pub(crate) struct AtomReadGuard<'a> {
|
|||||||
}
|
}
|
||||||
impl<'a> AtomReadGuard<'a> {
|
impl<'a> AtomReadGuard<'a> {
|
||||||
async fn new(id: api::AtomId) -> Self {
|
async fn new(id: api::AtomId) -> Self {
|
||||||
let guard = ctx().get_or_default::<ObjStore>().objects.read().await;
|
let guard = get_obj_store().objects.read().await;
|
||||||
if guard.get(&id).is_none() {
|
if guard.get(&id).is_none() {
|
||||||
panic!("Received invalid atom ID: {id:?}");
|
panic!("Received invalid atom ID: {id:?}");
|
||||||
}
|
}
|
||||||
@@ -73,50 +71,62 @@ impl Deref for AtomReadGuard<'_> {
|
|||||||
|
|
||||||
/// Remove an atom from the store
|
/// Remove an atom from the store
|
||||||
pub(crate) async fn take_atom(id: api::AtomId) -> Box<dyn DynOwnedAtom> {
|
pub(crate) async fn take_atom(id: api::AtomId) -> Box<dyn DynOwnedAtom> {
|
||||||
let mut g = ctx().get_or_default::<ObjStore>().objects.write().await;
|
let mut g = get_obj_store().objects.write().await;
|
||||||
g.remove(&id).unwrap_or_else(|| panic!("Received invalid atom ID: {}", id.0))
|
g.remove(&id).unwrap_or_else(|| {
|
||||||
|
let name = dyn_cted().inst().card().name();
|
||||||
|
panic!("{name} received invalid atom ID: {}", id.0)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OwnedAtomDynfo<T: OwnedAtom> {
|
pub(crate) struct OwnedAtomOps<T: OwnedAtom> {
|
||||||
msbuild: MethodSetBuilder<T>,
|
msbuild: MethodSetBuilder<T>,
|
||||||
ms: OnceCell<MethodSet<T>>,
|
ms: OnceCell<MethodSet<T>>,
|
||||||
}
|
}
|
||||||
impl<T: OwnedAtom> AtomDynfo for OwnedAtomDynfo<T> {
|
impl<A: OwnedAtom> AtomOps for OwnedAtomOps<A> {
|
||||||
fn tid(&self) -> TypeId { TypeId::of::<T>() }
|
fn tid(&self) -> TypeId { TypeId::of::<A>() }
|
||||||
fn name(&self) -> &'static str { type_name::<T>() }
|
fn name(&self) -> &'static str { type_name::<A>() }
|
||||||
fn decode<'a>(&'a self, AtomCtx(data, ..): AtomCtx<'a>) -> LocalBoxFuture<'a, Box<dyn Any>> {
|
fn decode<'a>(&'a self, AtomCtx(data, ..): AtomCtx<'a>) -> LocalBoxFuture<'a, Box<dyn Any>> {
|
||||||
Box::pin(async {
|
Box::pin(async { Box::new(<A as Atomic>::Data::decode_slice(&mut &data[..])) as Box<dyn Any> })
|
||||||
Box::new(<T as AtomCard>::Data::decode(Pin::new(&mut &data[..])).await) as Box<dyn Any>
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
fn call(&self, AtomCtx(_, id): AtomCtx, arg: Expr) -> LocalBoxFuture<'_, GExpr> {
|
fn call(&self, AtomCtx(_, id): AtomCtx, arg: Expr) -> LocalBoxFuture<'_, GExpr> {
|
||||||
Box::pin(async move { take_atom(id.unwrap()).await.dyn_call(arg).await })
|
Box::pin(async move {
|
||||||
|
writeln!(
|
||||||
|
log("msg"),
|
||||||
|
"owned call {} {}",
|
||||||
|
take_first(&AtomReadGuard::new(id.unwrap()).await.dyn_print().await, false),
|
||||||
|
take_first(&arg.print(&FmtCtxImpl::default()).await, true),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
take_atom(id.unwrap()).await.dyn_call(arg).await
|
||||||
|
})
|
||||||
}
|
}
|
||||||
fn call_ref<'a>(&'a self, AtomCtx(_, id): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> {
|
fn call_ref<'a>(&'a self, AtomCtx(_, id): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> {
|
||||||
Box::pin(async move { AtomReadGuard::new(id.unwrap()).await.dyn_call_ref(arg).await })
|
Box::pin(async move {
|
||||||
|
writeln!(
|
||||||
|
log("msg"),
|
||||||
|
"owned call_ref {} {}",
|
||||||
|
take_first(&AtomReadGuard::new(id.unwrap()).await.dyn_print().await, false),
|
||||||
|
take_first(&arg.print(&FmtCtxImpl::default()).await, true),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
AtomReadGuard::new(id.unwrap()).await.dyn_call_ref(arg).await
|
||||||
|
})
|
||||||
}
|
}
|
||||||
fn print(&self, AtomCtx(_, id): AtomCtx<'_>) -> LocalBoxFuture<'_, FmtUnit> {
|
fn print(&self, AtomCtx(_, id): AtomCtx<'_>) -> LocalBoxFuture<'_, FmtUnit> {
|
||||||
Box::pin(async move { AtomReadGuard::new(id.unwrap()).await.dyn_print().await })
|
Box::pin(async move { AtomReadGuard::new(id.unwrap()).await.dyn_print().await })
|
||||||
}
|
}
|
||||||
fn handle_req<'a, 'b: 'a, 'c: 'a>(
|
fn handle_req_ref<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
AtomCtx(_, id): AtomCtx,
|
AtomCtx(_, id): AtomCtx<'a>,
|
||||||
key: Sym,
|
key: Sym,
|
||||||
req: Pin<&'b mut dyn AsyncRead>,
|
req: Box<dyn orchid_base::ReqReader<'a> + 'a>,
|
||||||
rep: Pin<&'c mut dyn AsyncWrite>,
|
|
||||||
) -> LocalBoxFuture<'a, bool> {
|
) -> LocalBoxFuture<'a, bool> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let a = AtomReadGuard::new(id.unwrap()).await;
|
let a = AtomReadGuard::new(id.unwrap()).await;
|
||||||
let ms = self.ms.get_or_init(self.msbuild.pack()).await;
|
let ms = self.ms.get_or_init(self.msbuild.pack()).await;
|
||||||
ms.dispatch(a.as_any_ref().downcast_ref().unwrap(), key, req, rep).await
|
ms.dispatch(a.as_any_ref().downcast_ref().unwrap(), key, req).await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn command<'a>(
|
|
||||||
&'a self,
|
|
||||||
AtomCtx(_, id): AtomCtx<'a>,
|
|
||||||
) -> LocalBoxFuture<'a, OrcRes<Option<GExpr>>> {
|
|
||||||
Box::pin(async move { take_atom(id.unwrap()).await.dyn_command().await })
|
|
||||||
}
|
|
||||||
fn drop(&self, AtomCtx(_, id): AtomCtx) -> LocalBoxFuture<'_, ()> {
|
fn drop(&self, AtomCtx(_, id): AtomCtx) -> LocalBoxFuture<'_, ()> {
|
||||||
Box::pin(async move { take_atom(id.unwrap()).await.dyn_free().await })
|
Box::pin(async move { take_atom(id.unwrap()).await.dyn_free().await })
|
||||||
}
|
}
|
||||||
@@ -127,23 +137,38 @@ impl<T: OwnedAtom> AtomDynfo for OwnedAtomDynfo<T> {
|
|||||||
) -> LocalBoxFuture<'a, Option<Vec<Expr>>> {
|
) -> LocalBoxFuture<'a, Option<Vec<Expr>>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let id = id.unwrap();
|
let id = id.unwrap();
|
||||||
id.encode(write.as_mut()).await;
|
id.encode(write.as_mut()).await.unwrap();
|
||||||
AtomReadGuard::new(id).await.dyn_serialize(write).await
|
AtomReadGuard::new(id).await.dyn_serialize(write).await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn deserialize<'a>(&'a self, data: &'a [u8], refs: &'a [Expr]) -> LocalBoxFuture<'a, api::Atom> {
|
fn deserialize<'a>(
|
||||||
|
&'a self,
|
||||||
|
data: &'a [u8],
|
||||||
|
refs: &'a [Expr],
|
||||||
|
) -> LocalBoxFuture<'a, api::LocalAtom> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let refs = T::Refs::from_iter(refs.iter().cloned());
|
let refs = A::Refs::from_iter(refs.iter().cloned());
|
||||||
let obj = T::deserialize(DeserCtxImpl(data), refs).await;
|
let obj = A::deserialize(DeserCtxImpl(data), refs).await;
|
||||||
obj._factory().build().await
|
obj._factory().build().await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read from the buffer populated by a previous call to [OwnedAtom::serialize]
|
||||||
pub trait DeserializeCtx: Sized {
|
pub trait DeserializeCtx: Sized {
|
||||||
|
/// Read a value from the head of the buffer
|
||||||
fn read<T: Decode>(&mut self) -> impl Future<Output = T>;
|
fn read<T: Decode>(&mut self) -> impl Future<Output = T>;
|
||||||
|
/// Check if the buffer is empty
|
||||||
fn is_empty(&self) -> bool;
|
fn is_empty(&self) -> bool;
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// if the buffer isn't empty
|
||||||
fn assert_empty(&self) { assert!(self.is_empty(), "Bytes found after decoding") }
|
fn assert_empty(&self) { assert!(self.is_empty(), "Bytes found after decoding") }
|
||||||
|
/// Decode the only value in the buffer
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// if the buffer has more data after the value was read
|
||||||
fn decode<T: Decode>(&mut self) -> impl Future<Output = T> {
|
fn decode<T: Decode>(&mut self) -> impl Future<Output = T> {
|
||||||
async {
|
async {
|
||||||
let t = self.read().await;
|
let t = self.read().await;
|
||||||
@@ -155,10 +180,12 @@ pub trait DeserializeCtx: Sized {
|
|||||||
|
|
||||||
struct DeserCtxImpl<'a>(&'a [u8]);
|
struct DeserCtxImpl<'a>(&'a [u8]);
|
||||||
impl DeserializeCtx for DeserCtxImpl<'_> {
|
impl DeserializeCtx for DeserCtxImpl<'_> {
|
||||||
async fn read<T: Decode>(&mut self) -> T { T::decode(Pin::new(&mut self.0)).await }
|
async fn read<T: Decode>(&mut self) -> T { T::decode(Pin::new(&mut self.0)).await.unwrap() }
|
||||||
fn is_empty(&self) -> bool { self.0.is_empty() }
|
fn is_empty(&self) -> bool { self.0.is_empty() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Various collections of expr's that distinguish how many references the type
|
||||||
|
/// holds. See [OwnedAtom::Refs] for the list of permitted values
|
||||||
pub trait RefSet {
|
pub trait RefSet {
|
||||||
fn from_iter<I: Iterator<Item = Expr> + ExactSizeIterator>(refs: I) -> Self;
|
fn from_iter<I: Iterator<Item = Expr> + ExactSizeIterator>(refs: I) -> Self;
|
||||||
fn to_vec(self) -> Vec<Expr>;
|
fn to_vec(self) -> Vec<Expr>;
|
||||||
@@ -191,10 +218,12 @@ impl<const N: usize> RefSet for [Expr; N] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Atoms that have a [Drop]
|
/// Atoms that have a [Drop]. Internal mutability is allowed for optimization
|
||||||
|
/// purposes, but new references to [Expr] should not be added to avoid
|
||||||
|
/// reference loops
|
||||||
pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
|
pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
|
||||||
/// If serializable, the collection that best stores subexpression references
|
/// If serializable, the collection that best stores subexpression references
|
||||||
/// for this atom.
|
/// for this type.
|
||||||
///
|
///
|
||||||
/// - `()` for no subexppressions,
|
/// - `()` for no subexppressions,
|
||||||
/// - `[Expr; N]` for a static number of subexpressions
|
/// - `[Expr; N]` for a static number of subexpressions
|
||||||
@@ -202,30 +231,34 @@ pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
|
|||||||
/// - `Never` if not serializable
|
/// - `Never` if not serializable
|
||||||
///
|
///
|
||||||
/// If this isn't `Never`, you must override the default, panicking
|
/// If this isn't `Never`, you must override the default, panicking
|
||||||
/// `serialize` and `deserialize` implementation
|
/// [Self::serialize] and [Self::deserialize] implementation
|
||||||
type Refs: RefSet;
|
type Refs: RefSet;
|
||||||
|
/// Obtain serializable representation
|
||||||
fn val(&self) -> impl Future<Output = Cow<'_, Self::Data>>;
|
fn val(&self) -> impl Future<Output = Cow<'_, Self::Data>>;
|
||||||
|
/// Apply as a function while a different reference to the value exists.
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn call_ref(&self, arg: Expr) -> impl Future<Output = GExpr> {
|
fn call_ref(&self, arg: Expr) -> impl Future<Output: ToExpr> {
|
||||||
async move { bot(err_not_callable().await) }
|
async move { bot(err_not_callable(&self.dyn_print().await).await) }
|
||||||
}
|
}
|
||||||
fn call(self, arg: Expr) -> impl Future<Output = GExpr> {
|
/// Apply as a function and consume
|
||||||
|
fn call(self, arg: Expr) -> impl Future<Output: ToExpr> {
|
||||||
async {
|
async {
|
||||||
let gcl = self.call_ref(arg).await;
|
let gcl = self.call_ref(arg).await.to_gen().await;
|
||||||
self.free().await;
|
self.free().await;
|
||||||
gcl
|
gcl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(unused_variables)]
|
/// Drop and perform any cleanup. Unlike Rust's [Drop::drop], this is
|
||||||
fn command(self) -> impl Future<Output = OrcRes<Option<GExpr>>> {
|
/// guaranteed to be called
|
||||||
async move { Err(err_not_command().await) }
|
|
||||||
}
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn free(self) -> impl Future<Output = ()> { async {} }
|
fn free(self) -> impl Future<Output = ()> { async {} }
|
||||||
|
/// Debug-print. This is the final fallback for Orchid's
|
||||||
|
/// `std::string::to_str`.
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn print_atom<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> impl Future<Output = FmtUnit> {
|
fn print_atom<'a>(&'a self, c: &'a (impl FmtCtx + ?Sized + 'a)) -> impl Future<Output = FmtUnit> {
|
||||||
async { format!("OwnedAtom({})", type_name::<Self>()).into() }
|
async { format!("OwnedAtom({})", type_name::<Self>()).into() }
|
||||||
}
|
}
|
||||||
|
/// Serialize this object. If the object is serializable you must override
|
||||||
|
/// this function, otherwise set [Self::Refs] to [Never].
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn serialize(
|
fn serialize(
|
||||||
&self,
|
&self,
|
||||||
@@ -234,6 +267,8 @@ pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
|
|||||||
assert_serializable::<Self>();
|
assert_serializable::<Self>();
|
||||||
async { panic!("Either implement serialize or set Refs to Never for {}", type_name::<Self>()) }
|
async { panic!("Either implement serialize or set Refs to Never for {}", type_name::<Self>()) }
|
||||||
}
|
}
|
||||||
|
/// Deserialize this object. If the object is serializable you must override
|
||||||
|
/// this function, otherwise set [Self::Refs] to [Never]
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn deserialize(dctx: impl DeserializeCtx, refs: Self::Refs) -> impl Future<Output = Self> {
|
fn deserialize(dctx: impl DeserializeCtx, refs: Self::Refs) -> impl Future<Output = Self> {
|
||||||
assert_serializable::<Self>();
|
assert_serializable::<Self>();
|
||||||
@@ -243,18 +278,17 @@ pub trait OwnedAtom: Atomic<Variant = OwnedVariant> + Any + Clone + 'static {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Debug-assert that the object opted in to serialization
|
||||||
fn assert_serializable<T: OwnedAtom>() {
|
fn assert_serializable<T: OwnedAtom>() {
|
||||||
static MSG: &str = "The extension scaffold is broken, Never Refs should prevent serialization";
|
static MSG: &str = "The extension scaffold is broken, Never Refs should prevent serialization";
|
||||||
assert_ne!(TypeId::of::<T::Refs>(), TypeId::of::<Never>(), "{MSG}");
|
debug_assert_ne!(TypeId::of::<T::Refs>(), TypeId::of::<Never>(), "{MSG}");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DynOwnedAtom: DynClone + 'static {
|
pub(crate) trait DynOwnedAtom: DynClone + 'static {
|
||||||
fn atom_tid(&self) -> TypeId;
|
|
||||||
fn as_any_ref(&self) -> &dyn Any;
|
fn as_any_ref(&self) -> &dyn Any;
|
||||||
fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn AsyncWrite>) -> LocalBoxFuture<'a, ()>;
|
fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn AsyncWrite>) -> LocalBoxFuture<'a, ()>;
|
||||||
fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr>;
|
fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr>;
|
||||||
fn dyn_call(self: Box<Self>, arg: Expr) -> LocalBoxFuture<'static, GExpr>;
|
fn dyn_call(self: Box<Self>, arg: Expr) -> LocalBoxFuture<'static, GExpr>;
|
||||||
fn dyn_command(self: Box<Self>) -> LocalBoxFuture<'static, OrcRes<Option<GExpr>>>;
|
|
||||||
fn dyn_free(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
|
fn dyn_free(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
|
||||||
fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit>;
|
fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit>;
|
||||||
fn dyn_serialize<'a>(
|
fn dyn_serialize<'a>(
|
||||||
@@ -263,23 +297,19 @@ pub trait DynOwnedAtom: DynClone + 'static {
|
|||||||
) -> LocalBoxFuture<'a, Option<Vec<Expr>>>;
|
) -> LocalBoxFuture<'a, Option<Vec<Expr>>>;
|
||||||
}
|
}
|
||||||
impl<T: OwnedAtom> DynOwnedAtom for T {
|
impl<T: OwnedAtom> DynOwnedAtom for T {
|
||||||
fn atom_tid(&self) -> TypeId { TypeId::of::<T>() }
|
|
||||||
fn as_any_ref(&self) -> &dyn Any { self }
|
fn as_any_ref(&self) -> &dyn Any { self }
|
||||||
fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn AsyncWrite>) -> LocalBoxFuture<'a, ()> {
|
fn encode<'a>(&'a self, buffer: Pin<&'a mut dyn AsyncWrite>) -> LocalBoxFuture<'a, ()> {
|
||||||
async { self.val().await.as_ref().encode(buffer).await }.boxed_local()
|
async { self.val().await.as_ref().encode(buffer).await.unwrap() }.boxed_local()
|
||||||
}
|
}
|
||||||
fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr> {
|
fn dyn_call_ref(&self, arg: Expr) -> LocalBoxFuture<'_, GExpr> {
|
||||||
self.call_ref(arg).boxed_local()
|
async { self.call_ref(arg).await.to_gen().await }.boxed_local()
|
||||||
}
|
}
|
||||||
fn dyn_call(self: Box<Self>, arg: Expr) -> LocalBoxFuture<'static, GExpr> {
|
fn dyn_call(self: Box<Self>, arg: Expr) -> LocalBoxFuture<'static, GExpr> {
|
||||||
self.call(arg).boxed_local()
|
async { self.call(arg).await.to_gen().await }.boxed_local()
|
||||||
}
|
|
||||||
fn dyn_command(self: Box<Self>) -> LocalBoxFuture<'static, OrcRes<Option<GExpr>>> {
|
|
||||||
self.command().boxed_local()
|
|
||||||
}
|
}
|
||||||
fn dyn_free(self: Box<Self>) -> LocalBoxFuture<'static, ()> { self.free().boxed_local() }
|
fn dyn_free(self: Box<Self>) -> LocalBoxFuture<'static, ()> { self.free().boxed_local() }
|
||||||
fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit> {
|
fn dyn_print(&self) -> LocalBoxFuture<'_, FmtUnit> {
|
||||||
async move { self.print_atom(&FmtCtxImpl { i: &i() }).await }.boxed_local()
|
async move { self.print_atom(&FmtCtxImpl::default()).await }.boxed_local()
|
||||||
}
|
}
|
||||||
fn dyn_serialize<'a>(
|
fn dyn_serialize<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
@@ -294,22 +324,26 @@ impl<T: OwnedAtom> DynOwnedAtom for T {
|
|||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct ObjStore {
|
pub(crate) struct ObjStore {
|
||||||
pub(crate) next_id: AtomicU64,
|
pub(crate) next_id: RefCell<u64>,
|
||||||
pub(crate) objects: RwLock<MemoMap<api::AtomId, Box<dyn DynOwnedAtom>>>,
|
pub(crate) objects: RwLock<MemoMap<api::AtomId, Box<dyn DynOwnedAtom>>>,
|
||||||
}
|
}
|
||||||
impl SysCtxEntry for ObjStore {}
|
|
||||||
|
|
||||||
pub async fn own<A: OwnedAtom>(typ: &TAtom<A>) -> A {
|
task_local! {
|
||||||
let g = ctx().get_or_default::<ObjStore>().objects.read().await;
|
static OBJ_STORE: Rc<ObjStore>;
|
||||||
let atom_id = typ.untyped.atom.drop.expect("Owned atoms always have a drop ID");
|
|
||||||
let dyn_atom =
|
|
||||||
g.get(&atom_id).expect("Atom ID invalid; atom type probably not owned by this crate");
|
|
||||||
dyn_atom.as_any_ref().downcast_ref().cloned().expect("The ID should imply a type as well")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_obj_store<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
|
||||||
|
Box::pin(OBJ_STORE.scope(Rc::new(ObjStore::default()), fut))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_obj_store() -> Rc<ObjStore> {
|
||||||
|
OBJ_STORE.try_with(|store| store.clone()).expect("Owned atom store not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Debug-print the entire object store. Most useful if the interpreter refuses
|
||||||
|
/// to shut down due to apparent refloops
|
||||||
pub async fn debug_print_obj_store(show_atoms: bool) {
|
pub async fn debug_print_obj_store(show_atoms: bool) {
|
||||||
let ctx = ctx();
|
let store = get_obj_store();
|
||||||
let store = ctx.get_or_default::<ObjStore>();
|
|
||||||
let keys = store.objects.read().await.keys().cloned().collect_vec();
|
let keys = store.objects.read().await.keys().cloned().collect_vec();
|
||||||
let mut message = "Atoms in store:".to_string();
|
let mut message = "Atoms in store:".to_string();
|
||||||
if !show_atoms {
|
if !show_atoms {
|
||||||
@@ -326,5 +360,5 @@ pub async fn debug_print_obj_store(show_atoms: bool) {
|
|||||||
message += &format!("\n{k:?} -> {}", take_first(&atom.dyn_print().await, true));
|
message += &format!("\n{k:?} -> {}", take_first(&atom.dyn_print().await, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eprintln!("{message}")
|
writeln!(log("debug"), "{message}").await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,107 +3,94 @@ use std::future::Future;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use async_once_cell::OnceCell;
|
use async_once_cell::OnceCell;
|
||||||
|
use futures::AsyncWrite;
|
||||||
use futures::future::LocalBoxFuture;
|
use futures::future::LocalBoxFuture;
|
||||||
use futures::{AsyncRead, AsyncWrite, FutureExt};
|
|
||||||
use orchid_api_traits::{Coding, enc_vec};
|
use orchid_api_traits::{Coding, enc_vec};
|
||||||
use orchid_base::error::OrcRes;
|
use orchid_base::{FmtUnit, Sym, log};
|
||||||
use orchid_base::format::FmtUnit;
|
|
||||||
use orchid_base::name::Sym;
|
|
||||||
|
|
||||||
use crate::api;
|
|
||||||
use crate::atom::{
|
|
||||||
AtomCard, AtomCtx, AtomDynfo, AtomFactory, Atomic, AtomicFeaturesImpl, AtomicVariant, MethodSet,
|
|
||||||
MethodSetBuilder, err_not_callable, err_not_command, get_info,
|
|
||||||
};
|
|
||||||
use crate::context::ctx;
|
|
||||||
use crate::expr::Expr;
|
|
||||||
use crate::gen_expr::{GExpr, bot};
|
use crate::gen_expr::{GExpr, bot};
|
||||||
use crate::system_ctor::CtedObj;
|
use crate::{
|
||||||
|
AtomCtx, AtomFactory, AtomOps, Atomic, AtomicFeaturesImpl, AtomicVariant, DynSystemCardExt, Expr,
|
||||||
|
MethodSet, MethodSetBuilder, api, dyn_cted, err_not_callable,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Value of [Atomic::Variant] for a type that implements [ThinAtom]
|
||||||
pub struct ThinVariant;
|
pub struct ThinVariant;
|
||||||
impl AtomicVariant for ThinVariant {}
|
impl AtomicVariant for ThinVariant {}
|
||||||
impl<A: ThinAtom + Atomic<Variant = ThinVariant>> AtomicFeaturesImpl<ThinVariant> for A {
|
impl<A: ThinAtom + Atomic<Variant = ThinVariant>> AtomicFeaturesImpl<ThinVariant> for A {
|
||||||
fn _factory(self) -> AtomFactory {
|
fn _factory(self) -> AtomFactory {
|
||||||
AtomFactory::new(async move || {
|
AtomFactory::new(type_name::<A>().to_string(), async move || {
|
||||||
let (id, _) = get_info::<A>(ctx().get::<CtedObj>().inst().card());
|
let (id, _) = dyn_cted().inst().card().ops::<A>();
|
||||||
let mut buf = enc_vec(&id).await;
|
let mut buf = enc_vec(&id);
|
||||||
self.encode(Pin::new(&mut buf)).await;
|
self.encode_vec(&mut buf);
|
||||||
api::Atom { drop: None, data: api::AtomData(buf), owner: ctx().sys_id() }
|
api::LocalAtom { drop: None, data: api::AtomData(buf) }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn _info() -> Self::_Info { ThinAtomDynfo { msbuild: Self::reg_reqs(), ms: OnceCell::new() } }
|
fn _info() -> Self::_Info { ThinAtomOps { msbuild: Self::reg_methods(), ms: OnceCell::new() } }
|
||||||
type _Info = ThinAtomDynfo<Self>;
|
type _Info = ThinAtomOps<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ThinAtomDynfo<T: ThinAtom> {
|
pub(crate) struct ThinAtomOps<T: ThinAtom> {
|
||||||
msbuild: MethodSetBuilder<T>,
|
msbuild: MethodSetBuilder<T>,
|
||||||
ms: OnceCell<MethodSet<T>>,
|
ms: OnceCell<MethodSet<T>>,
|
||||||
}
|
}
|
||||||
impl<T: ThinAtom> AtomDynfo for ThinAtomDynfo<T> {
|
impl<T: ThinAtom> AtomOps for ThinAtomOps<T> {
|
||||||
fn print<'a>(&self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit> {
|
fn print<'a>(&self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, FmtUnit> {
|
||||||
Box::pin(async move { T::decode(Pin::new(&mut &buf[..])).await.print().await })
|
Box::pin(async move { T::decode_slice(&mut &buf[..]).print().await })
|
||||||
}
|
}
|
||||||
fn tid(&self) -> TypeId { TypeId::of::<T>() }
|
fn tid(&self) -> TypeId { TypeId::of::<T>() }
|
||||||
fn name(&self) -> &'static str { type_name::<T>() }
|
fn name(&self) -> &'static str { type_name::<T>() }
|
||||||
fn decode<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>) -> LocalBoxFuture<'a, Box<dyn Any>> {
|
fn decode<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>) -> LocalBoxFuture<'a, Box<dyn Any>> {
|
||||||
Box::pin(async { Box::new(T::decode(Pin::new(&mut &buf[..])).await) as Box<dyn Any> })
|
Box::pin(async { Box::new(T::decode_slice(&mut &buf[..])) as Box<dyn Any> })
|
||||||
}
|
}
|
||||||
fn call<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> {
|
fn call<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> {
|
||||||
Box::pin(async move { T::decode(Pin::new(&mut &buf[..])).await.call(arg).await })
|
Box::pin(async move { T::decode_slice(&mut &buf[..]).call(arg).await })
|
||||||
}
|
}
|
||||||
fn call_ref<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> {
|
fn call_ref<'a>(&'a self, AtomCtx(buf, ..): AtomCtx<'a>, arg: Expr) -> LocalBoxFuture<'a, GExpr> {
|
||||||
Box::pin(async move { T::decode(Pin::new(&mut &buf[..])).await.call(arg).await })
|
Box::pin(async move { T::decode_slice(&mut &buf[..]).call(arg).await })
|
||||||
}
|
}
|
||||||
fn handle_req<'a, 'm1: 'a, 'm2: 'a>(
|
fn handle_req_ref<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
AtomCtx(buf, _): AtomCtx<'a>,
|
AtomCtx(buf, ..): AtomCtx<'a>,
|
||||||
key: Sym,
|
key: Sym,
|
||||||
req: Pin<&'m1 mut dyn AsyncRead>,
|
req: Box<dyn orchid_base::ReqReader<'a> + 'a>,
|
||||||
rep: Pin<&'m2 mut dyn AsyncWrite>,
|
|
||||||
) -> LocalBoxFuture<'a, bool> {
|
) -> LocalBoxFuture<'a, bool> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let ms = self.ms.get_or_init(self.msbuild.pack()).await;
|
let ms = self.ms.get_or_init(self.msbuild.pack()).await;
|
||||||
ms.dispatch(&T::decode(Pin::new(&mut &buf[..])).await, key, req, rep).await
|
ms.dispatch(&T::decode_slice(&mut &buf[..]), key, req).await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn command<'a>(
|
|
||||||
&'a self,
|
|
||||||
AtomCtx(buf, _): AtomCtx<'a>,
|
|
||||||
) -> LocalBoxFuture<'a, OrcRes<Option<GExpr>>> {
|
|
||||||
async move { T::decode(Pin::new(&mut &buf[..])).await.command().await }.boxed_local()
|
|
||||||
}
|
|
||||||
fn serialize<'a, 'b: 'a>(
|
fn serialize<'a, 'b: 'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
ctx: AtomCtx<'a>,
|
ctx: AtomCtx<'a>,
|
||||||
write: Pin<&'b mut dyn AsyncWrite>,
|
write: Pin<&'b mut dyn AsyncWrite>,
|
||||||
) -> LocalBoxFuture<'a, Option<Vec<Expr>>> {
|
) -> LocalBoxFuture<'a, Option<Vec<Expr>>> {
|
||||||
Box::pin(async {
|
Box::pin(async {
|
||||||
T::decode(Pin::new(&mut &ctx.0[..])).await.encode(write).await;
|
T::decode_slice(&mut &ctx.0[..]).encode(write).await.unwrap();
|
||||||
Some(Vec::new())
|
Some(Vec::new())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn deserialize<'a>(&'a self, data: &'a [u8], refs: &'a [Expr]) -> LocalBoxFuture<'a, api::Atom> {
|
fn deserialize<'a>(
|
||||||
|
&'a self,
|
||||||
|
data: &'a [u8],
|
||||||
|
refs: &'a [Expr],
|
||||||
|
) -> LocalBoxFuture<'a, api::LocalAtom> {
|
||||||
assert!(refs.is_empty(), "Refs found when deserializing thin atom");
|
assert!(refs.is_empty(), "Refs found when deserializing thin atom");
|
||||||
Box::pin(async { T::decode(Pin::new(&mut &data[..])).await._factory().build().await })
|
Box::pin(async { T::decode_slice(&mut &data[..])._factory().build().await })
|
||||||
}
|
}
|
||||||
fn drop<'a>(&'a self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, ()> {
|
fn drop<'a>(&'a self, AtomCtx(buf, _): AtomCtx<'a>) -> LocalBoxFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let string_self = T::decode(Pin::new(&mut &buf[..])).await.print().await;
|
let string_self = T::decode_slice(&mut &buf[..]).print().await;
|
||||||
writeln!(ctx().logger(), "Received drop signal for non-drop atom {string_self:?}");
|
writeln!(log("warn"), "Received drop signal for non-drop atom {string_self:?}").await;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ThinAtom:
|
/// A simple value that is serializable and does not reference any other values
|
||||||
AtomCard<Data = Self> + Atomic<Variant = ThinVariant> + Coding + Send + Sync + 'static
|
pub trait ThinAtom: Atomic<Data = Self> + Atomic<Variant = ThinVariant> + Coding + 'static {
|
||||||
{
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn call(&self, arg: Expr) -> impl Future<Output = GExpr> {
|
fn call(&self, arg: Expr) -> impl Future<Output = GExpr> {
|
||||||
async move { bot(err_not_callable().await) }
|
async move { bot(err_not_callable(&self.print().await).await) }
|
||||||
}
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn command(&self) -> impl Future<Output = OrcRes<Option<GExpr>>> {
|
|
||||||
async move { Err(err_not_command().await) }
|
|
||||||
}
|
}
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn print(&self) -> impl Future<Output = FmtUnit> {
|
fn print(&self) -> impl Future<Output = FmtUnit> {
|
||||||
|
|||||||
56
orchid-extension/src/binary.rs
Normal file
56
orchid-extension/src/binary.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use futures::future::LocalBoxFuture;
|
||||||
|
use orchid_base::future_to_vt;
|
||||||
|
|
||||||
|
use crate::{ExtPort, ExtensionBuilder, api};
|
||||||
|
|
||||||
|
pub type ExtCx = api::binary::ExtensionContext;
|
||||||
|
|
||||||
|
struct Spawner(api::binary::SpawnerBin);
|
||||||
|
impl Drop for Spawner {
|
||||||
|
fn drop(&mut self) { (self.0.drop)(self.0.data) }
|
||||||
|
}
|
||||||
|
impl Spawner {
|
||||||
|
pub fn spawn(&self, delay: Duration, fut: LocalBoxFuture<'static, ()>) {
|
||||||
|
(self.0.spawn)(self.0.data, delay.as_millis().try_into().unwrap(), future_to_vt(fut))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn orchid_extension_main_body(cx: ExtCx, builder: ExtensionBuilder) {
|
||||||
|
let spawner = Rc::new(Spawner(cx.spawner));
|
||||||
|
let spawner2 = spawner.clone();
|
||||||
|
spawner2.spawn(
|
||||||
|
Duration::ZERO,
|
||||||
|
Box::pin(builder.run(ExtPort {
|
||||||
|
input: Box::pin(cx.input),
|
||||||
|
output: Box::pin(cx.output),
|
||||||
|
log: Box::pin(cx.log),
|
||||||
|
spawn: Rc::new(move |delay, fut| spawner.spawn(delay, fut)),
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate entrypoint for the dylib extension loader
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// #[macro_use]
|
||||||
|
/// use orchid_extension::dylib_main;
|
||||||
|
/// use orchid_extension::entrypoint::ExtensionBuilder;
|
||||||
|
///
|
||||||
|
/// dylib_main! {
|
||||||
|
/// ExtensionBuilder::new("orchid-std::main")
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! dylib_main {
|
||||||
|
($builder:expr) => {
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn orchid_extension_main(cx: ::orchid_api::binary::ExtensionContext) {
|
||||||
|
$crate::binary::orchid_extension_main_body(cx, $builder);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
49
orchid-extension/src/cmd_atom.rs
Normal file
49
orchid-extension/src/cmd_atom.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use futures::future::LocalBoxFuture;
|
||||||
|
use never::Never;
|
||||||
|
use orchid_base::{Receipt, ReqHandle, ReqHandleExt};
|
||||||
|
|
||||||
|
use crate::gen_expr::{GExpr, new_atom, serialize};
|
||||||
|
use crate::std_reqs::RunCommand;
|
||||||
|
use crate::{Atomic, MethodSetBuilder, OwnedAtom, OwnedVariant, Supports, ToExpr};
|
||||||
|
|
||||||
|
pub trait AsyncFnDyn {
|
||||||
|
fn call<'a>(&'a self) -> LocalBoxFuture<'a, Option<GExpr>>;
|
||||||
|
}
|
||||||
|
impl<T: AsyncFn() -> Option<GExpr>> AsyncFnDyn for T {
|
||||||
|
fn call<'a>(&'a self) -> LocalBoxFuture<'a, Option<GExpr>> { Box::pin(async { (self)().await }) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CmdAtom(Rc<dyn AsyncFnDyn>);
|
||||||
|
impl Atomic for CmdAtom {
|
||||||
|
type Data = ();
|
||||||
|
type Variant = OwnedVariant;
|
||||||
|
fn reg_methods() -> MethodSetBuilder<Self> { MethodSetBuilder::new().handle::<RunCommand>() }
|
||||||
|
}
|
||||||
|
impl Supports<RunCommand> for CmdAtom {
|
||||||
|
async fn handle<'a>(
|
||||||
|
&self,
|
||||||
|
hand: Box<dyn ReqHandle<'a> + '_>,
|
||||||
|
req: RunCommand,
|
||||||
|
) -> std::io::Result<Receipt<'a>> {
|
||||||
|
let reply = self.0.call().await;
|
||||||
|
match reply {
|
||||||
|
None => hand.reply(&req, &None).await,
|
||||||
|
Some(next) => hand.reply(&req, &Some(serialize(next).await)).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl OwnedAtom for CmdAtom {
|
||||||
|
type Refs = Never;
|
||||||
|
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cmd<R: ToExpr>(f: impl AsyncFn() -> Option<R> + Clone + 'static) -> GExpr {
|
||||||
|
new_atom(CmdAtom(Rc::new(async move || match f().await {
|
||||||
|
None => None,
|
||||||
|
Some(r) => Some(r.to_gen().await),
|
||||||
|
})))
|
||||||
|
}
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
use std::any::{Any, TypeId, type_name};
|
|
||||||
use std::fmt;
|
|
||||||
use std::num::NonZero;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use memo_map::MemoMap;
|
|
||||||
use orchid_base::builtin::Spawner;
|
|
||||||
use orchid_base::interner::Interner;
|
|
||||||
use orchid_base::logging::Logger;
|
|
||||||
use orchid_base::reqnot::ReqNot;
|
|
||||||
use task_local::task_local;
|
|
||||||
|
|
||||||
use crate::api;
|
|
||||||
use crate::system_ctor::CtedObj;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct SysCtx(Rc<MemoMap<TypeId, Box<dyn Any>>>);
|
|
||||||
impl SysCtx {
|
|
||||||
pub fn new(
|
|
||||||
id: api::SysId,
|
|
||||||
i: Interner,
|
|
||||||
reqnot: ReqNot<api::ExtMsgSet>,
|
|
||||||
spawner: Spawner,
|
|
||||||
logger: Logger,
|
|
||||||
cted: CtedObj,
|
|
||||||
) -> Self {
|
|
||||||
let this = Self(Rc::new(MemoMap::new()));
|
|
||||||
this.add(id).add(i).add(reqnot).add(spawner).add(logger).add(cted);
|
|
||||||
this
|
|
||||||
}
|
|
||||||
pub fn add<T: SysCtxEntry>(&self, t: T) -> &Self {
|
|
||||||
assert!(self.0.insert(TypeId::of::<T>(), Box::new(t)), "Key already exists");
|
|
||||||
self
|
|
||||||
}
|
|
||||||
pub fn get_or_insert<T: SysCtxEntry>(&self, f: impl FnOnce() -> T) -> &T {
|
|
||||||
(self.0.get_or_insert_owned(TypeId::of::<T>(), || Box::new(f())).downcast_ref())
|
|
||||||
.expect("Keyed by TypeId")
|
|
||||||
}
|
|
||||||
pub fn get_or_default<T: SysCtxEntry + Default>(&self) -> &T { self.get_or_insert(T::default) }
|
|
||||||
pub fn try_get<T: SysCtxEntry>(&self) -> Option<&T> {
|
|
||||||
Some(self.0.get(&TypeId::of::<T>())?.downcast_ref().expect("Keyed by TypeId"))
|
|
||||||
}
|
|
||||||
pub fn get<T: SysCtxEntry>(&self) -> &T {
|
|
||||||
self.try_get().unwrap_or_else(|| panic!("Context {} missing", type_name::<T>()))
|
|
||||||
}
|
|
||||||
/// Shorthand to get the messaging link
|
|
||||||
pub fn reqnot(&self) -> &ReqNot<api::ExtMsgSet> { self.get::<ReqNot<api::ExtMsgSet>>() }
|
|
||||||
/// Shorthand to get the system ID
|
|
||||||
pub fn sys_id(&self) -> api::SysId { *self.get::<api::SysId>() }
|
|
||||||
/// Spawn a task that will eventually be executed asynchronously
|
|
||||||
pub fn spawn(&self, f: impl Future<Output = ()> + 'static) {
|
|
||||||
(self.get::<Spawner>())(Box::pin(CTX.scope(self.clone(), f)))
|
|
||||||
}
|
|
||||||
/// Shorthand to get the logger
|
|
||||||
pub fn logger(&self) -> &Logger { self.get::<Logger>() }
|
|
||||||
/// Shorthand to get the constructed system object
|
|
||||||
pub fn cted(&self) -> &CtedObj { self.get::<CtedObj>() }
|
|
||||||
}
|
|
||||||
impl fmt::Debug for SysCtx {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "SysCtx({:?})", self.sys_id())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub trait SysCtxEntry: 'static + Sized {}
|
|
||||||
impl SysCtxEntry for api::SysId {}
|
|
||||||
impl SysCtxEntry for ReqNot<api::ExtMsgSet> {}
|
|
||||||
impl SysCtxEntry for Spawner {}
|
|
||||||
impl SysCtxEntry for CtedObj {}
|
|
||||||
impl SysCtxEntry for Logger {}
|
|
||||||
impl SysCtxEntry for Interner {}
|
|
||||||
|
|
||||||
task_local! {
|
|
||||||
static CTX: SysCtx;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn with_ctx<F: Future>(ctx: SysCtx, f: F) -> F::Output { CTX.scope(ctx, f).await }
|
|
||||||
pub fn ctx() -> SysCtx { CTX.get() }
|
|
||||||
|
|
||||||
/// Shorthand to get the [Interner] instance
|
|
||||||
pub fn i() -> Interner { ctx().get::<Interner>().clone() }
|
|
||||||
|
|
||||||
pub fn mock_ctx() -> SysCtx {
|
|
||||||
let ctx = SysCtx(Rc::default());
|
|
||||||
ctx
|
|
||||||
.add(Logger::new(api::LogStrategy::StdErr))
|
|
||||||
.add(Interner::new_master())
|
|
||||||
.add::<Spawner>(Rc::new(|_| panic!("Cannot fork in test environment")))
|
|
||||||
.add(api::SysId(NonZero::<u16>::MIN));
|
|
||||||
ctx
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,20 @@ use std::future::Future;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
|
use futures::future::FusedFuture;
|
||||||
use never::Never;
|
use never::Never;
|
||||||
use orchid_base::error::{OrcErrv, OrcRes, mk_errv};
|
use orchid_base::{Format, OrcErrv, OrcRes, Pos, fmt, is, mk_errv};
|
||||||
use orchid_base::location::Pos;
|
|
||||||
use trait_set::trait_set;
|
use trait_set::trait_set;
|
||||||
|
|
||||||
use crate::atom::{AtomicFeatures, ForeignAtom, TAtom, ToAtom};
|
use crate::gen_expr::{GExpr, bot};
|
||||||
use crate::context::i;
|
use crate::{AtomicFeatures, Expr, ExprKind, ForeignAtom, TAtom};
|
||||||
use crate::expr::Expr;
|
|
||||||
use crate::gen_expr::{GExpr, atom, bot};
|
|
||||||
|
|
||||||
|
/// Values that may be converted from certain specific Orchid expressions
|
||||||
pub trait TryFromExpr: Sized {
|
pub trait TryFromExpr: Sized {
|
||||||
|
/// Attempt to cast a generic Orchid expression reference to a concrete value.
|
||||||
|
/// Note that this cannot evaluate the expression, and if it is not already
|
||||||
|
/// evaluated, it will simply fail. Use [crate::ExecHandle::exec] inside
|
||||||
|
/// [crate::exec] to wait for an expression to be evaluated
|
||||||
fn try_from_expr(expr: Expr) -> impl Future<Output = OrcRes<Self>>;
|
fn try_from_expr(expr: Expr) -> impl Future<Output = OrcRes<Self>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,14 +29,19 @@ impl<T: TryFromExpr, U: TryFromExpr> TryFromExpr for (T, U) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn err_not_atom(pos: Pos) -> OrcErrv {
|
/// Error raised when a composite expression was assumed to be an
|
||||||
mk_errv(i().i("Expected an atom").await, "This expression is not an atom", [pos])
|
/// [crate::Atomic], or if the expression was not evaluated yet
|
||||||
|
async fn err_not_atom(pos: Pos, value: &impl Format) -> OrcErrv {
|
||||||
|
mk_errv(is("Expected an atom").await, format!("{} is not an atom", fmt(value).await), [pos])
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFromExpr for ForeignAtom {
|
impl TryFromExpr for ForeignAtom {
|
||||||
async fn try_from_expr(expr: Expr) -> OrcRes<Self> {
|
async fn try_from_expr(expr: Expr) -> OrcRes<Self> {
|
||||||
match expr.atom().await {
|
if let ExprKind::Bottom(err) = &expr.data().await.kind {
|
||||||
Err(ex) => Err(err_not_atom(ex.data().await.pos.clone()).await),
|
return Err(err.clone());
|
||||||
|
}
|
||||||
|
match expr.clone().atom().await {
|
||||||
|
Err(ex) => Err(err_not_atom(ex.data().await.pos.clone(), &expr).await),
|
||||||
Ok(f) => Ok(f),
|
Ok(f) => Ok(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,21 +50,59 @@ impl TryFromExpr for ForeignAtom {
|
|||||||
impl<A: AtomicFeatures> TryFromExpr for TAtom<A> {
|
impl<A: AtomicFeatures> TryFromExpr for TAtom<A> {
|
||||||
async fn try_from_expr(expr: Expr) -> OrcRes<Self> {
|
async fn try_from_expr(expr: Expr) -> OrcRes<Self> {
|
||||||
let f = ForeignAtom::try_from_expr(expr).await?;
|
let f = ForeignAtom::try_from_expr(expr).await?;
|
||||||
match f.clone().downcast::<A>().await {
|
match f.clone().downcast::<A>() {
|
||||||
Ok(a) => Ok(a),
|
Ok(a) => Ok(a),
|
||||||
Err(e) => Err(e.mk_err().await),
|
Err(e) => Err(e.mk_err().await),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Values that are convertible to an Orchid expression. This could mean that
|
||||||
|
/// the value owns an [Expr] or it may involve more complex operations
|
||||||
|
///
|
||||||
|
/// [ToExpr] is also implemented for [orchid_base::Sym] where it converts to a
|
||||||
|
/// reference to the constant by that name
|
||||||
pub trait ToExpr {
|
pub trait ToExpr {
|
||||||
|
/// Inline the value in an expression returned from a function or included in
|
||||||
|
/// the const tree returned by [crate::System::env]
|
||||||
fn to_gen(self) -> impl Future<Output = GExpr>;
|
fn to_gen(self) -> impl Future<Output = GExpr>;
|
||||||
|
/// Convert the value into a freestanding expression
|
||||||
fn to_expr(self) -> impl Future<Output = Expr>
|
fn to_expr(self) -> impl Future<Output = Expr>
|
||||||
where Self: Sized {
|
where Self: Sized {
|
||||||
async { self.to_gen().await.create().await }
|
async { self.to_gen().await.create().await }
|
||||||
}
|
}
|
||||||
|
fn boxed<'a>(self) -> Box<dyn ToExprDyn + 'a>
|
||||||
|
where Self: Sized + 'a {
|
||||||
|
Box::new(self)
|
||||||
|
}
|
||||||
|
fn clonable_boxed<'a>(self) -> Box<dyn ClonableToExprDyn + 'a>
|
||||||
|
where Self: Clone + Sized + 'a {
|
||||||
|
Box::new(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper for a future that implements [ToExpr]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct ToExprFuture<F>(pub F);
|
||||||
|
|
||||||
|
impl<F: Future<Output: ToExpr>> ToExpr for ToExprFuture<F> {
|
||||||
|
async fn to_gen(self) -> GExpr { self.0.await.to_gen().await }
|
||||||
|
async fn to_expr(self) -> Expr
|
||||||
|
where Self: Sized {
|
||||||
|
self.0.await.to_expr().await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<F: FusedFuture> FusedFuture for ToExprFuture<F> {
|
||||||
|
fn is_terminated(&self) -> bool { self.0.is_terminated() }
|
||||||
|
}
|
||||||
|
impl<F: Future> Future for ToExprFuture<F> {
|
||||||
|
type Output = F::Output;
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
|
||||||
|
unsafe { self.map_unchecked_mut(|this| &mut this.0) }.poll(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type-erased [ToExpr]
|
||||||
pub trait ToExprDyn {
|
pub trait ToExprDyn {
|
||||||
fn to_gen_dyn<'a>(self: Box<Self>) -> Pin<Box<dyn Future<Output = GExpr> + 'a>>
|
fn to_gen_dyn<'a>(self: Box<Self>) -> Pin<Box<dyn Future<Output = GExpr> + 'a>>
|
||||||
where Self: 'a;
|
where Self: 'a;
|
||||||
@@ -75,6 +121,8 @@ impl<T: ToExpr> ToExprDyn for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
trait_set! {
|
trait_set! {
|
||||||
|
/// type-erased [ToExpr] and [Clone]. Needed for a value to be
|
||||||
|
/// included in [crate::System::env]
|
||||||
pub trait ClonableToExprDyn = ToExprDyn + DynClone;
|
pub trait ClonableToExprDyn = ToExprDyn + DynClone;
|
||||||
}
|
}
|
||||||
impl ToExpr for Box<dyn ToExprDyn> {
|
impl ToExpr for Box<dyn ToExprDyn> {
|
||||||
@@ -107,10 +155,6 @@ impl<T: ToExpr> ToExpr for OrcRes<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: ToAtom> ToExpr for A {
|
|
||||||
async fn to_gen(self) -> GExpr { atom(self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToExpr for Never {
|
impl ToExpr for Never {
|
||||||
async fn to_gen(self) -> GExpr { match self {} }
|
async fn to_gen(self) -> GExpr { match self {} }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::any::type_name;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -7,21 +8,18 @@ use futures::lock::Mutex;
|
|||||||
use futures::stream::{self, LocalBoxStream};
|
use futures::stream::{self, LocalBoxStream};
|
||||||
use futures::{FutureExt, SinkExt, StreamExt};
|
use futures::{FutureExt, SinkExt, StreamExt};
|
||||||
use never::Never;
|
use never::Never;
|
||||||
use orchid_base::error::OrcRes;
|
use orchid_base::{FmtCtx, FmtUnit, OrcRes};
|
||||||
|
|
||||||
use crate::atom::Atomic;
|
use crate::gen_expr::{GExpr, call, lam, new_atom, seq};
|
||||||
use crate::atom_owned::{OwnedAtom, OwnedVariant};
|
use crate::{Atomic, Expr, OwnedAtom, OwnedVariant, ToExpr, TryFromExpr};
|
||||||
use crate::conv::{ToExpr, TryFromExpr};
|
|
||||||
use crate::expr::Expr;
|
|
||||||
use crate::gen_expr::{GExpr, arg, call, lambda, seq};
|
|
||||||
|
|
||||||
enum Command {
|
enum Command {
|
||||||
Execute(GExpr, Sender<Expr>),
|
Execute(GExpr, Sender<Expr>),
|
||||||
Register(GExpr, Sender<Expr>),
|
|
||||||
Halt(GExpr),
|
Halt(GExpr),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BuilderCoroutineData {
|
struct BuilderCoroutineData {
|
||||||
|
name: &'static str,
|
||||||
receiver: Mutex<LocalBoxStream<'static, Command>>,
|
receiver: Mutex<LocalBoxStream<'static, Command>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,23 +29,17 @@ impl BuilderCoroutine {
|
|||||||
pub async fn run(self) -> GExpr {
|
pub async fn run(self) -> GExpr {
|
||||||
let cmd = self.0.receiver.lock().await.next().await;
|
let cmd = self.0.receiver.lock().await.next().await;
|
||||||
match cmd {
|
match cmd {
|
||||||
None => panic!("Before the stream ends, we should have gotten a Halt"),
|
None => panic!("Exec handle dropped and coroutine blocked instead of returning"),
|
||||||
Some(Command::Halt(expr)) => expr,
|
Some(Command::Halt(expr)) => expr,
|
||||||
Some(Command::Execute(expr, reply)) => call(
|
Some(Command::Execute(expr, reply)) =>
|
||||||
lambda(0, [seq(
|
call(lam(async |x| seq(x, call(new_atom(Replier { reply, builder: self }), x)).await), expr)
|
||||||
[arg(0)],
|
.await,
|
||||||
call(Replier { reply, builder: self }.to_gen().await, [arg(0)]),
|
|
||||||
)]),
|
|
||||||
[expr],
|
|
||||||
),
|
|
||||||
Some(Command::Register(expr, reply)) =>
|
|
||||||
call(Replier { reply, builder: self }.to_gen().await, [expr]),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Replier {
|
pub(crate) struct Replier {
|
||||||
reply: Sender<Expr>,
|
reply: Sender<Expr>,
|
||||||
builder: BuilderCoroutine,
|
builder: BuilderCoroutine,
|
||||||
}
|
}
|
||||||
@@ -58,18 +50,24 @@ impl Atomic for Replier {
|
|||||||
impl OwnedAtom for Replier {
|
impl OwnedAtom for Replier {
|
||||||
type Refs = Never;
|
type Refs = Never;
|
||||||
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||||
async fn call(mut self, arg: Expr) -> GExpr {
|
async fn call(mut self, arg: Expr) -> impl ToExpr {
|
||||||
self.reply.send(arg).await.expect("What the heck");
|
self.reply.send(arg).await.expect("Resolution request dropped after sending");
|
||||||
std::mem::drop(self.reply);
|
std::mem::drop(self.reply);
|
||||||
self.builder.run().await
|
self.builder.run().await
|
||||||
}
|
}
|
||||||
|
async fn print_atom<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||||
|
format!("Replier<{}>", self.builder.0.name).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A long-lived async context that can yield to the executor. The expression
|
||||||
|
/// representing an in-progress exec block is not serializable.
|
||||||
pub async fn exec<R: ToExpr>(f: impl for<'a> AsyncFnOnce(ExecHandle<'a>) -> R + 'static) -> GExpr {
|
pub async fn exec<R: ToExpr>(f: impl for<'a> AsyncFnOnce(ExecHandle<'a>) -> R + 'static) -> GExpr {
|
||||||
let (cmd_snd, cmd_recv) = channel(0);
|
let (cmd_snd, cmd_recv) = channel(0);
|
||||||
let halt = async { Command::Halt(f(ExecHandle(cmd_snd, PhantomData)).await.to_gen().await) }
|
let halt =
|
||||||
.into_stream();
|
async { Command::Halt(f(ExecHandle(cmd_snd, PhantomData)).await.to_gen().await) }.into_stream();
|
||||||
let coro = BuilderCoroutine(Rc::new(BuilderCoroutineData {
|
let coro = BuilderCoroutine(Rc::new(BuilderCoroutineData {
|
||||||
|
name: type_name::<R>(),
|
||||||
receiver: Mutex::new(stream::select(halt, cmd_recv).boxed_local()),
|
receiver: Mutex::new(stream::select(halt, cmd_recv).boxed_local()),
|
||||||
}));
|
}));
|
||||||
coro.run().await
|
coro.run().await
|
||||||
@@ -77,16 +75,14 @@ pub async fn exec<R: ToExpr>(f: impl for<'a> AsyncFnOnce(ExecHandle<'a>) -> R +
|
|||||||
|
|
||||||
static WEIRD_DROP_ERR: &str = "Coroutine dropped while we are being polled somehow";
|
static WEIRD_DROP_ERR: &str = "Coroutine dropped while we are being polled somehow";
|
||||||
|
|
||||||
|
/// The handle an [exec] callback uses to yield to the executor
|
||||||
pub struct ExecHandle<'a>(Sender<Command>, PhantomData<&'a ()>);
|
pub struct ExecHandle<'a>(Sender<Command>, PhantomData<&'a ()>);
|
||||||
impl ExecHandle<'_> {
|
impl ExecHandle<'_> {
|
||||||
|
/// Yield to the executor by resolving to an expression that normalizes the
|
||||||
|
/// value and then calls the continuation of the body with the result.
|
||||||
pub async fn exec<T: TryFromExpr>(&mut self, val: impl ToExpr) -> OrcRes<T> {
|
pub async fn exec<T: TryFromExpr>(&mut self, val: impl ToExpr) -> OrcRes<T> {
|
||||||
let (reply_snd, mut reply_recv) = channel(1);
|
let (reply_snd, mut reply_recv) = channel(1);
|
||||||
self.0.send(Command::Execute(val.to_gen().await, reply_snd)).await.expect(WEIRD_DROP_ERR);
|
self.0.send(Command::Execute(val.to_gen().await, reply_snd)).await.expect(WEIRD_DROP_ERR);
|
||||||
T::try_from_expr(reply_recv.next().await.expect(WEIRD_DROP_ERR)).await
|
T::try_from_expr(reply_recv.next().await.expect(WEIRD_DROP_ERR)).await
|
||||||
}
|
}
|
||||||
pub async fn register(&mut self, val: impl ToExpr) -> Expr {
|
|
||||||
let (reply_snd, mut reply_recv) = channel(1);
|
|
||||||
self.0.send(Command::Register(val.to_gen().await, reply_snd)).await.expect(WEIRD_DROP_ERR);
|
|
||||||
reply_recv.next().await.expect(WEIRD_DROP_ERR)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,381 +1,398 @@
|
|||||||
|
use std::any::Any;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::num::NonZero;
|
use std::num::NonZero;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use futures::channel::mpsc::{Receiver, Sender, channel};
|
|
||||||
use futures::future::{LocalBoxFuture, join_all};
|
use futures::future::{LocalBoxFuture, join_all};
|
||||||
use futures::lock::Mutex;
|
use futures::{AsyncWriteExt, StreamExt, stream};
|
||||||
use futures::{FutureExt, SinkExt, StreamExt, stream, stream_select};
|
|
||||||
use futures_locks::RwLock;
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use orchid_api_traits::{Decode, UnderRoot, enc_vec};
|
use orchid_api_traits::{Decode, Encode, Request, UnderRoot, enc_vec};
|
||||||
use orchid_base::builtin::{ExtInit, ExtPort, Spawner};
|
use orchid_async_utils::{Handle, to_task};
|
||||||
use orchid_base::char_filter::{char_filter_match, char_filter_union, mk_char_filter};
|
use orchid_base::{
|
||||||
use orchid_base::clone;
|
Client, ClientExt, CommCtx, Comment, MsgReader, MsgReaderExt, ReqHandleExt, ReqReaderExt,
|
||||||
use orchid_base::error::Reporter;
|
Snippet, Sym, TokenVariant, Witness, char_filter_match, char_filter_union, es, io_comm, is, log,
|
||||||
use orchid_base::interner::{Interner, Tok};
|
mk_char_filter, try_with_reporter, ttv_from_api, with_interner, with_logger, with_stash,
|
||||||
use orchid_base::logging::Logger;
|
};
|
||||||
use orchid_base::name::Sym;
|
|
||||||
use orchid_base::parse::{Comment, Snippet};
|
|
||||||
use orchid_base::reqnot::{ReqNot, RequestHandle, Requester};
|
|
||||||
use orchid_base::tree::{TokenVariant, ttv_from_api};
|
|
||||||
use substack::Substack;
|
use substack::Substack;
|
||||||
use trait_set::trait_set;
|
use task_local::task_local;
|
||||||
|
|
||||||
use crate::api;
|
use crate::gen_expr::serialize;
|
||||||
use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId};
|
use crate::interner::new_interner;
|
||||||
use crate::atom_owned::take_atom;
|
use crate::logger::LoggerImpl;
|
||||||
use crate::context::{SysCtx, ctx, i, with_ctx};
|
use crate::tree::{TreeIntoApiCtxImpl, get_lazy, with_lazy_member_store};
|
||||||
use crate::expr::{BorrowedExprStore, Expr, ExprHandle};
|
use crate::trivial_req::TrivialReqCycle;
|
||||||
use crate::lexer::{LexContext, ekey_cascade, ekey_not_applicable};
|
use crate::{
|
||||||
use crate::parser::{PTokTree, ParsCtx, get_const, linev_into_api};
|
AtomCtx, AtomTypeId, BorrowedExprStore, CtedObj, DynSystemCardExt, DynSystemCtor, Expr,
|
||||||
use crate::system::atom_by_idx;
|
ExprHandle, ExtPort, LexContext, PTokTree, ParsCtx, SysCtx, SystemCtor, api, dyn_cted,
|
||||||
use crate::system_ctor::{CtedObj, DynSystemCtor};
|
ekey_cascade, ekey_not_applicable, get_const, linev_into_api, resolve_atom_type, take_atom,
|
||||||
use crate::tree::{LazyMemberFactory, TreeIntoApiCtxImpl};
|
with_funs_ctx, with_obj_store, with_parsed_const_ctx, with_refl_roots, with_sys,
|
||||||
|
};
|
||||||
|
|
||||||
pub type ExtReq<'a> = RequestHandle<'a, api::ExtMsgSet>;
|
task_local::task_local! {
|
||||||
pub type ExtReqNot = ReqNot<api::ExtMsgSet>;
|
static CLIENT: Rc<dyn Client>;
|
||||||
|
static CTX: Rc<RefCell<Option<CommCtx>>>;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ExtensionData {
|
fn get_client() -> Rc<dyn Client> { CLIENT.get() }
|
||||||
|
|
||||||
|
/// Do not expect any more requests or notifications, exit once all pending
|
||||||
|
/// requests settle
|
||||||
|
pub async fn exit() {
|
||||||
|
let cx = CTX.get().borrow_mut().take();
|
||||||
|
cx.unwrap().exit().await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the client used for global [request] and [notify] functions within the
|
||||||
|
/// runtime of this future
|
||||||
|
pub async fn with_comm<F: Future>(c: Rc<dyn Client>, ctx: CommCtx, fut: F) -> F::Output {
|
||||||
|
CLIENT.scope(c, CTX.scope(Rc::new(RefCell::new(Some(ctx))), fut)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
task_local! {
|
||||||
|
static MUTE_REPLY: ();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Silence replies within this block even if the `msg` log channel is active to
|
||||||
|
/// prevent excessive log noise.
|
||||||
|
pub async fn mute_reply<F: Future>(f: F) -> F::Output { MUTE_REPLY.scope((), f).await }
|
||||||
|
|
||||||
|
/// Send a request through the global client's [ClientExt::request]
|
||||||
|
pub async fn request<T: Request + UnderRoot<Root = api::ExtHostReq>>(t: T) -> T::Response {
|
||||||
|
let req_str = if MUTE_REPLY.try_with(|b| *b).is_err() { format!("{t:?}") } else { String::new() };
|
||||||
|
let response = get_client().request(t).await.unwrap();
|
||||||
|
if MUTE_REPLY.try_with(|b| *b).is_err() {
|
||||||
|
let ext = dyn_cted().inst().card().name();
|
||||||
|
writeln!(log("msg"), "{ext} {req_str} got response {response:?}").await;
|
||||||
|
}
|
||||||
|
response
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a notification through the global client's [ClientExt::notify]
|
||||||
|
pub async fn notify<T: UnderRoot<Root = api::ExtHostNotif>>(t: T) {
|
||||||
|
get_client().notify(t).await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SystemRecord {
|
||||||
|
cted: CtedObj,
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemTable = RefCell<HashMap<api::SysId, Rc<SystemRecord>>>;
|
||||||
|
|
||||||
|
task_local! {
|
||||||
|
static SYSTEM_TABLE: SystemTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn with_sys_record<F: Future>(id: api::SysId, fut: F) -> F::Output {
|
||||||
|
let cted = SYSTEM_TABLE.with(|tbl| tbl.borrow().get(&id).expect("Invalid sys ID").cted.clone());
|
||||||
|
with_sys(SysCtx(id, cted), fut).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Context that can be attached to a [Future] using [task_local]
|
||||||
|
pub trait ContextModifier: 'static {
|
||||||
|
fn apply<'a>(self: Box<Self>, fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: AsyncFnOnce(LocalBoxFuture<'_, ()>) + 'static> ContextModifier for F {
|
||||||
|
fn apply<'a>(self: Box<Self>, fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
|
||||||
|
Box::pin((self)(fut))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait DynTaskHandle: 'static {
|
||||||
|
fn abort(self: Box<Self>);
|
||||||
|
fn join(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn Any>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
task_local! {
|
||||||
|
pub(crate) static SPAWN:
|
||||||
|
Rc<dyn Fn(Duration, LocalBoxFuture<'static, Box<dyn Any>>) -> Box<dyn DynTaskHandle> + 'static>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle for a task that is not associated with a particular pending request
|
||||||
|
/// or past notification
|
||||||
|
pub struct TaskHandle<T>(Box<dyn DynTaskHandle>, PhantomData<T>);
|
||||||
|
impl<T: 'static> TaskHandle<T> {
|
||||||
|
/// Immediately stop working on the task. Unlike in Tokio's abort, this is a
|
||||||
|
/// guarantee
|
||||||
|
pub fn abort(self) { self.0.abort(); }
|
||||||
|
/// Stop working on the task and return the nested future. The distinction
|
||||||
|
/// between this and waiting until the task is complete without reparenting it
|
||||||
|
/// is significant for the purpose of [task_local] context
|
||||||
|
pub async fn join(self) -> T { *self.0.join().await.downcast().unwrap() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn a future that is not associated with a pending request or a past
|
||||||
|
/// notification. Pending tasks are cancelled and dropped when the extension
|
||||||
|
/// exits
|
||||||
|
pub fn spawn<F: Future<Output: 'static> + 'static>(delay: Duration, f: F) -> TaskHandle<F::Output> {
|
||||||
|
SPAWN.with(|spawn| {
|
||||||
|
TaskHandle(spawn(delay, Box::pin(async { Box::new(f.await) as Box<dyn Any> })), PhantomData)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DynTaskHandle for Handle<Box<dyn Any>> {
|
||||||
|
fn abort(self: Box<Self>) { Self::abort(&self); }
|
||||||
|
fn join(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn Any>> { Box::pin(Self::join(*self)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A new Orchid extension as specified in loaders. An extension is a unit of
|
||||||
|
/// distribution and its name serves for debugging purposes primarily. In
|
||||||
|
/// contrast, [SystemCtor] is a unit of features of which [ExtensionBuilder] may
|
||||||
|
/// contain multiple
|
||||||
|
pub struct ExtensionBuilder {
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
pub systems: &'static [&'static dyn DynSystemCtor],
|
pub systems: Vec<Box<dyn DynSystemCtor>>,
|
||||||
|
pub context: Vec<Box<dyn ContextModifier>>,
|
||||||
}
|
}
|
||||||
impl ExtensionData {
|
impl ExtensionBuilder {
|
||||||
pub fn new(name: &'static str, systems: &'static [&'static dyn DynSystemCtor]) -> Self {
|
/// Create a new extension
|
||||||
Self { name, systems }
|
pub fn new(name: &'static str) -> Self { Self { name, systems: Vec::new(), context: Vec::new() } }
|
||||||
|
/// Add a system to the extension
|
||||||
|
pub fn system(mut self, ctor: impl SystemCtor) -> Self {
|
||||||
|
self.systems.push(Box::new(ctor) as Box<_>);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
/// Add some [task_local] state to the extension. Bear in mind that distinct
|
||||||
|
/// [crate::System] instances should not visibly affect each other
|
||||||
pub enum MemberRecord {
|
pub fn add_context(&mut self, fun: impl ContextModifier) {
|
||||||
Gen(Vec<Tok<String>>, LazyMemberFactory),
|
self.context.push(Box::new(fun) as Box<_>);
|
||||||
Res,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SystemRecord {
|
|
||||||
lazy_members: Mutex<HashMap<api::TreeId, MemberRecord>>,
|
|
||||||
ctx: SysCtx,
|
|
||||||
}
|
|
||||||
|
|
||||||
trait_set! {
|
|
||||||
pub trait WithAtomRecordCallback<'a, T> = AsyncFnOnce(
|
|
||||||
Box<dyn AtomDynfo>,
|
|
||||||
AtomTypeId,
|
|
||||||
&'a [u8]
|
|
||||||
) -> T
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn with_atom_record<'a, F: Future<Output = SysCtx>, T>(
|
|
||||||
get_sys_ctx: &impl Fn(api::SysId) -> F,
|
|
||||||
atom: &'a api::Atom,
|
|
||||||
cb: impl WithAtomRecordCallback<'a, T>,
|
|
||||||
) -> T {
|
|
||||||
let mut data = &atom.data.0[..];
|
|
||||||
let ctx = get_sys_ctx(atom.owner).await;
|
|
||||||
let inst = ctx.get::<CtedObj>().inst();
|
|
||||||
let id = AtomTypeId::decode(Pin::new(&mut data)).await;
|
|
||||||
let atom_record = atom_by_idx(inst.card(), id.clone()).expect("Atom ID reserved");
|
|
||||||
with_ctx(ctx, async move { cb(atom_record, id, data).await }).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ExtensionOwner {
|
|
||||||
_interner_cell: Rc<RefCell<Option<Interner>>>,
|
|
||||||
_systems_lock: Rc<RwLock<HashMap<api::SysId, SystemRecord>>>,
|
|
||||||
out_recv: Mutex<Receiver<Vec<u8>>>,
|
|
||||||
out_send: Sender<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExtPort for ExtensionOwner {
|
|
||||||
fn send<'a>(&'a self, msg: &'a [u8]) -> LocalBoxFuture<'a, ()> {
|
|
||||||
Box::pin(async { self.out_send.clone().send(msg.to_vec()).boxed_local().await.unwrap() })
|
|
||||||
}
|
}
|
||||||
fn recv(&self) -> LocalBoxFuture<'_, Option<Vec<u8>>> {
|
/// Builder form of [Self::add_context]
|
||||||
Box::pin(async { self.out_recv.lock().await.next().await })
|
pub fn context(mut self, fun: impl ContextModifier) -> Self {
|
||||||
|
self.add_context(fun);
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
/// Start the extension on a message channel, blocking the task until the peer
|
||||||
|
/// on the other side drops the extension. Extension authors would typically
|
||||||
pub fn extension_init(
|
/// pass the prepared builder into some function that is responsible for
|
||||||
data: ExtensionData,
|
/// managing the [ExtPort]
|
||||||
host_header: api::HostHeader,
|
pub async fn run(mut self, mut ctx: ExtPort) {
|
||||||
spawner: Spawner,
|
self.add_context(with_funs_ctx);
|
||||||
) -> ExtInit {
|
self.add_context(with_parsed_const_ctx);
|
||||||
let api::HostHeader { log_strategy, msg_logs } = host_header;
|
self.add_context(with_obj_store);
|
||||||
let decls = (data.systems.iter().enumerate())
|
self.add_context(with_lazy_member_store);
|
||||||
|
self.add_context(with_refl_roots);
|
||||||
|
let spawn = ctx.spawn.clone();
|
||||||
|
let host_header = api::HostHeader::decode(ctx.input.as_mut()).await.unwrap();
|
||||||
|
let decls = (self.systems.iter().enumerate())
|
||||||
.map(|(id, sys)| (u16::try_from(id).expect("more than u16max system ctors"), sys))
|
.map(|(id, sys)| (u16::try_from(id).expect("more than u16max system ctors"), sys))
|
||||||
.map(|(id, sys)| sys.decl(api::SysDeclId(NonZero::new(id + 1).unwrap())))
|
.map(|(id, sys)| sys.decl(api::SysDeclId(NonZero::new(id + 1).unwrap())))
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
let systems_lock = Rc::new(RwLock::new(HashMap::<api::SysId, SystemRecord>::new()));
|
api::ExtensionHeader { name: self.name.to_string(), systems: decls.clone() }
|
||||||
let ext_header = api::ExtensionHeader { name: data.name.to_string(), systems: decls.clone() };
|
.encode(ctx.output.as_mut())
|
||||||
let (out_send, in_recv) = channel::<Vec<u8>>(1);
|
.await
|
||||||
let (in_send, out_recv) = channel::<Vec<u8>>(1);
|
.unwrap();
|
||||||
let (exit_send, exit_recv) = channel(1);
|
ctx.output.as_mut().flush().await.unwrap();
|
||||||
let logger = Logger::new(log_strategy);
|
let logger1 = LoggerImpl::from_api(&host_header.logger);
|
||||||
let msg_logger = Logger::new(msg_logs);
|
let logger2 = logger1.clone();
|
||||||
let interner_cell = Rc::new(RefCell::new(None::<Interner>));
|
let (client, comm_ctx, extension_srv) = io_comm(ctx.output, ctx.input);
|
||||||
let interner_weak = Rc::downgrade(&interner_cell);
|
// this future will be ready once the extension cleanly exits
|
||||||
let systems_weak = Rc::downgrade(&systems_lock);
|
let extension_fut = extension_srv.listen(
|
||||||
let get_ctx = clone!(systems_weak; move |id: api::SysId| clone!(systems_weak; async move {
|
async |n: Box<dyn MsgReader<'_>>| {
|
||||||
let systems =
|
let notif = n.read().await.unwrap();
|
||||||
systems_weak.upgrade().expect("System table dropped before request processing done");
|
match notif {
|
||||||
systems.read().await.get(&id).expect("System not found").ctx.clone()
|
api::HostExtNotif::Exit => exit().await,
|
||||||
}));
|
|
||||||
let init_ctx = {
|
|
||||||
clone!(interner_weak, spawner, logger);
|
|
||||||
move |id: api::SysId, cted: CtedObj, reqnot: ReqNot<api::ExtMsgSet>| {
|
|
||||||
clone!(interner_weak, spawner, logger; async move {
|
|
||||||
let interner_rc =
|
|
||||||
interner_weak.upgrade().expect("System construction order while shutting down");
|
|
||||||
let i = interner_rc.borrow().clone().expect("mk_ctx called very early, no interner!");
|
|
||||||
SysCtx::new(id, i, reqnot, spawner, logger, cted)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
};
|
Ok(())
|
||||||
let rn = ReqNot::<api::ExtMsgSet>::new(
|
|
||||||
msg_logger.clone(),
|
|
||||||
move |a, _| {
|
|
||||||
clone!(in_send mut);
|
|
||||||
Box::pin(async move { in_send.send(a.to_vec()).await.unwrap() })
|
|
||||||
},
|
},
|
||||||
{
|
async |mut reader| {
|
||||||
clone!(exit_send);
|
with_stash(async {
|
||||||
move |n, _| {
|
let req = reader.read_req().await.unwrap();
|
||||||
clone!(exit_send mut);
|
let handle = reader.finish().await;
|
||||||
async move {
|
// Atom printing is never reported because it generates too much
|
||||||
match n {
|
// noise
|
||||||
api::HostExtNotif::Exit => exit_send.send(()).await.unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.boxed_local()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
clone!(logger, get_ctx, init_ctx, systems_weak, interner_weak, decls, msg_logger);
|
|
||||||
move |hand, req| {
|
|
||||||
clone!(logger, get_ctx, init_ctx, systems_weak, interner_weak, decls, msg_logger);
|
|
||||||
async move {
|
|
||||||
let interner_cell = interner_weak.upgrade().expect("Interner dropped before request");
|
|
||||||
let interner =
|
|
||||||
interner_cell.borrow().clone().expect("Request arrived before interner set");
|
|
||||||
if !matches!(req, api::HostExtReq::AtomReq(api::AtomReq::AtomPrint(_))) {
|
if !matches!(req, api::HostExtReq::AtomReq(api::AtomReq::AtomPrint(_))) {
|
||||||
writeln!(msg_logger, "{} extension received request {req:?}", data.name);
|
writeln!(log("msg"), "{} extension received request {req:?}", self.name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
match req {
|
match req {
|
||||||
api::HostExtReq::SystemDrop(sys_drop) => {
|
api::HostExtReq::SystemDrop(sys_drop) => {
|
||||||
if let Some(rc) = systems_weak.upgrade() {
|
SYSTEM_TABLE.with(|l| l.borrow_mut().remove(&sys_drop.0));
|
||||||
mem::drop(rc.write().await.remove(&sys_drop.0))
|
handle.reply(&sys_drop, &()).await
|
||||||
}
|
|
||||||
hand.handle(&sys_drop, &()).await
|
|
||||||
},
|
},
|
||||||
api::HostExtReq::AtomDrop(atom_drop @ api::AtomDrop(sys_id, atom)) =>
|
api::HostExtReq::AtomDrop(atom_drop @ api::AtomDrop(sys_id, atom)) =>
|
||||||
with_ctx(get_ctx(sys_id).await, async move {
|
with_sys_record(sys_id, async {
|
||||||
take_atom(atom).await.dyn_free().await;
|
take_atom(atom).await.dyn_free().await;
|
||||||
hand.handle(&atom_drop, &()).await
|
handle.reply(&atom_drop, &()).await
|
||||||
})
|
})
|
||||||
.await,
|
.await,
|
||||||
api::HostExtReq::Ping(ping @ api::Ping) => hand.handle(&ping, &()).await,
|
api::HostExtReq::Ping(ping @ api::Ping) => handle.reply(&ping, &()).await,
|
||||||
api::HostExtReq::Sweep(sweep @ api::Sweep) =>
|
api::HostExtReq::Sweep(api::Sweep) => todo!(),
|
||||||
hand.handle(&sweep, &interner.sweep_replica().await).await,
|
|
||||||
api::HostExtReq::SysReq(api::SysReq::NewSystem(new_sys)) => {
|
api::HostExtReq::SysReq(api::SysReq::NewSystem(new_sys)) => {
|
||||||
let (sys_id, _) = (decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system))
|
let (ctor_idx, _) = (decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system))
|
||||||
.expect("NewSystem call received for invalid system");
|
.expect("NewSystem call received for invalid system");
|
||||||
let cted = data.systems[sys_id].new_system(&new_sys);
|
let cted = self.systems[ctor_idx].new_system(&new_sys);
|
||||||
with_ctx(init_ctx(new_sys.id, cted.clone(), hand.reqnot()).await, async move {
|
let record = Rc::new(SystemRecord { cted: cted.clone() });
|
||||||
|
SYSTEM_TABLE.with(|tbl| {
|
||||||
|
let mut g = tbl.borrow_mut();
|
||||||
|
g.insert(new_sys.id, record);
|
||||||
|
});
|
||||||
|
with_sys_record(new_sys.id, async {
|
||||||
let lex_filter =
|
let lex_filter =
|
||||||
cted.inst().dyn_lexers().iter().fold(api::CharFilter(vec![]), |cf, lx| {
|
cted.inst().dyn_lexers().iter().fold(api::CharFilter(vec![]), |cf, lx| {
|
||||||
char_filter_union(&cf, &mk_char_filter(lx.char_filter().iter().cloned()))
|
char_filter_union(&cf, &mk_char_filter(lx.char_filter().iter().cloned()))
|
||||||
});
|
});
|
||||||
let lazy_members = Mutex::new(HashMap::new());
|
|
||||||
let const_root = stream::iter(cted.inst().dyn_env().await)
|
let const_root = stream::iter(cted.inst().dyn_env().await)
|
||||||
.then(|mem| {
|
.then(async |mem| {
|
||||||
let lazy_mems = &lazy_members;
|
let name = is(&mem.name).await;
|
||||||
async move {
|
|
||||||
let name = i().i(&mem.name).await;
|
|
||||||
let mut tia_ctx = TreeIntoApiCtxImpl {
|
let mut tia_ctx = TreeIntoApiCtxImpl {
|
||||||
lazy_members: &mut *lazy_mems.lock().await,
|
|
||||||
basepath: &[],
|
basepath: &[],
|
||||||
path: Substack::Bottom.push(name.clone()),
|
path: Substack::Bottom.push(name.clone()),
|
||||||
};
|
};
|
||||||
(name.to_api(), mem.kind.into_api(&mut tia_ctx).await)
|
(name.to_api(), mem.kind.into_api(&mut tia_ctx).await)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
.await;
|
.await;
|
||||||
let prelude =
|
let prelude =
|
||||||
cted.inst().dyn_prelude().await.iter().map(|sym| sym.to_api()).collect();
|
cted.inst().dyn_prelude().await.iter().map(|sym| sym.to_api()).collect();
|
||||||
let record = SystemRecord { ctx: ctx(), lazy_members };
|
|
||||||
let systems = systems_weak.upgrade().expect("System constructed during shutdown");
|
|
||||||
systems.write().await.insert(new_sys.id, record);
|
|
||||||
let line_types = join_all(
|
let line_types = join_all(
|
||||||
(cted.inst().dyn_parsers().iter())
|
(cted.inst().dyn_parsers().iter())
|
||||||
.map(|p| async { interner.i(p.line_head()).await.to_api() }),
|
.map(async |p| is(p.line_head()).await.to_api()),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let response =
|
let response =
|
||||||
api::NewSystemResponse { lex_filter, const_root, line_types, prelude };
|
api::NewSystemResponse { lex_filter, const_root, line_types, prelude };
|
||||||
hand.handle(&new_sys, &response).await
|
handle.reply(&new_sys, &response).await
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
},
|
},
|
||||||
api::HostExtReq::GetMember(get_tree @ api::GetMember(sys_id, tree_id)) =>
|
api::HostExtReq::GetMember(get_tree @ api::GetMember(sys_id, tree_id)) =>
|
||||||
with_ctx(get_ctx(sys_id).await, async move {
|
with_sys_record(sys_id, async {
|
||||||
let systems = systems_weak.upgrade().expect("Member queried during shutdown");
|
let (path, tree) = get_lazy(tree_id).await;
|
||||||
let systems_g = systems.read().await;
|
let mut tia_ctx =
|
||||||
let mut lazy_members =
|
TreeIntoApiCtxImpl { path: Substack::Bottom, basepath: &path[..] };
|
||||||
systems_g.get(&sys_id).expect("System not found").lazy_members.lock().await;
|
handle.reply(&get_tree, &tree.into_api(&mut tia_ctx).await).await
|
||||||
let (path, cb) = match lazy_members.insert(tree_id, MemberRecord::Res) {
|
|
||||||
None => panic!("Tree for ID not found"),
|
|
||||||
Some(MemberRecord::Res) => panic!("This tree has already been transmitted"),
|
|
||||||
Some(MemberRecord::Gen(path, cb)) => (path, cb),
|
|
||||||
};
|
|
||||||
let tree = cb.build(Sym::new(path.clone(), &interner).await.unwrap()).await;
|
|
||||||
let mut tia_ctx = TreeIntoApiCtxImpl {
|
|
||||||
path: Substack::Bottom,
|
|
||||||
basepath: &path,
|
|
||||||
lazy_members: &mut lazy_members,
|
|
||||||
};
|
|
||||||
hand.handle(&get_tree, &tree.into_api(&mut tia_ctx).await).await
|
|
||||||
})
|
})
|
||||||
.await,
|
.await,
|
||||||
api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => {
|
api::HostExtReq::SysReq(api::SysReq::SysFwded(fwd)) => {
|
||||||
|
let fwd_tok = Witness::of(&fwd);
|
||||||
let api::SysFwded(sys_id, payload) = fwd;
|
let api::SysFwded(sys_id, payload) = fwd;
|
||||||
let ctx = get_ctx(sys_id).await;
|
with_sys_record(sys_id, async {
|
||||||
with_ctx(ctx.clone(), async move {
|
let mut reply = Vec::new();
|
||||||
let sys = ctx.cted().inst();
|
let req = TrivialReqCycle { req: &payload, rep: &mut reply };
|
||||||
sys.dyn_request(hand, payload).await
|
let _ = dyn_cted().inst().dyn_request(Box::new(req)).await;
|
||||||
|
handle.reply(fwd_tok, &reply).await
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
},
|
},
|
||||||
api::HostExtReq::LexExpr(lex @ api::LexExpr { sys, src, text, pos, id }) =>
|
api::HostExtReq::LexExpr(lex @ api::LexExpr { sys, src, text, pos, id }) =>
|
||||||
with_ctx(get_ctx(sys).await, async move {
|
with_sys_record(sys, async {
|
||||||
let text = Tok::from_api(text, &i()).await;
|
let text = es(text).await;
|
||||||
let src = Sym::from_api(src, &i()).await;
|
let src = Sym::from_api(src).await;
|
||||||
let rep = Reporter::new();
|
|
||||||
let expr_store = BorrowedExprStore::new();
|
let expr_store = BorrowedExprStore::new();
|
||||||
let trigger_char = text.chars().nth(pos as usize).unwrap();
|
let tail = &text[pos as usize..];
|
||||||
|
let trigger_char = tail.chars().next().unwrap();
|
||||||
let ekey_na = ekey_not_applicable().await;
|
let ekey_na = ekey_not_applicable().await;
|
||||||
let ekey_cascade = ekey_cascade().await;
|
let ekey_cascade = ekey_cascade().await;
|
||||||
let lexers = ctx().cted().inst().dyn_lexers();
|
let lexers = dyn_cted().inst().dyn_lexers();
|
||||||
for lx in lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char))
|
for lx in lexers.iter().filter(|l| char_filter_match(l.char_filter(), trigger_char))
|
||||||
{
|
{
|
||||||
let ctx = LexContext::new(&expr_store, &text, id, pos, src.clone(), &rep);
|
let ctx = LexContext::new(&expr_store, &text, id, pos, src.clone());
|
||||||
match lx.lex(&text[pos as usize..], &ctx).await {
|
match try_with_reporter(lx.lex(tail, &ctx)).await {
|
||||||
Err(e) if e.any(|e| *e == ekey_na) => continue,
|
Err(e) if e.any(|e| *e == ekey_na) => continue,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api()));
|
let eopt = e.keep_only(|e| *e != ekey_cascade).map(|e| Err(e.to_api()));
|
||||||
expr_store.dispose().await;
|
expr_store.dispose().await;
|
||||||
return hand.handle(&lex, &eopt).await;
|
return handle.reply(&lex, &eopt).await;
|
||||||
},
|
},
|
||||||
Ok((s, expr)) => {
|
Ok((s, expr)) => {
|
||||||
let expr = expr.into_api(&mut (), &mut ()).await;
|
let expr = join_all(
|
||||||
|
(expr.into_iter())
|
||||||
|
.map(|tok| async { tok.into_api(&mut (), &mut ()).await }),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
let pos = (text.len() - s.len()) as u32;
|
let pos = (text.len() - s.len()) as u32;
|
||||||
expr_store.dispose().await;
|
expr_store.dispose().await;
|
||||||
return hand.handle(&lex, &Some(Ok(api::LexedExpr { pos, expr }))).await;
|
return handle.reply(&lex, &Some(Ok(api::LexedExpr { pos, expr }))).await;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
writeln!(logger, "Got notified about n/a character '{trigger_char}'");
|
writeln!(log("warn"), "Got notified about n/a character '{trigger_char}'").await;
|
||||||
expr_store.dispose().await;
|
expr_store.dispose().await;
|
||||||
hand.handle(&lex, &None).await
|
handle.reply(&lex, &None).await
|
||||||
})
|
})
|
||||||
.await,
|
.await,
|
||||||
api::HostExtReq::ParseLine(pline) => {
|
api::HostExtReq::ParseLine(pline) => {
|
||||||
let api::ParseLine { module, src, exported, comments, sys, line, idx } = &pline;
|
let req = Witness::of(&pline);
|
||||||
with_ctx(get_ctx(*sys).await, async {
|
let api::ParseLine { module, src, exported, comments, sys, line, idx } = pline;
|
||||||
let parsers = ctx().cted().inst().dyn_parsers();
|
with_sys_record(sys, async {
|
||||||
let src = Sym::from_api(*src, &i()).await;
|
let parsers = dyn_cted().inst().dyn_parsers();
|
||||||
|
let src = Sym::from_api(src).await;
|
||||||
let comments =
|
let comments =
|
||||||
join_all(comments.iter().map(|c| Comment::from_api(c, src.clone(), &interner)))
|
join_all(comments.iter().map(|c| Comment::from_api(c, src.clone()))).await;
|
||||||
.await;
|
|
||||||
let expr_store = BorrowedExprStore::new();
|
let expr_store = BorrowedExprStore::new();
|
||||||
let line: Vec<PTokTree> =
|
let line: Vec<PTokTree> = ttv_from_api(line, &mut &expr_store, &mut (), &src).await;
|
||||||
ttv_from_api(line, &mut &expr_store, &mut (), &src, &i()).await;
|
|
||||||
let snip = Snippet::new(line.first().expect("Empty line"), &line);
|
let snip = Snippet::new(line.first().expect("Empty line"), &line);
|
||||||
let parser = parsers[*idx as usize];
|
let parser = parsers[idx as usize];
|
||||||
let module = Sym::from_api(*module, &i()).await;
|
let module = Sym::from_api(module).await;
|
||||||
let reporter = Reporter::new();
|
let pctx = ParsCtx::new(module);
|
||||||
let pctx = ParsCtx::new(module, &reporter);
|
let o_line =
|
||||||
let parse_res = parser.parse(pctx, *exported, comments, snip).await;
|
match try_with_reporter(parser.parse(pctx, exported, comments, snip)).await {
|
||||||
let o_line = match reporter.merge(parse_res) {
|
|
||||||
Err(e) => Err(e.to_api()),
|
Err(e) => Err(e.to_api()),
|
||||||
Ok(t) => Ok(linev_into_api(t).await),
|
Ok(t) => Ok(linev_into_api(t).await),
|
||||||
};
|
};
|
||||||
mem::drop(line);
|
mem::drop(line);
|
||||||
expr_store.dispose().await;
|
expr_store.dispose().await;
|
||||||
hand.handle(&pline, &o_line).await
|
handle.reply(req, &o_line).await
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
},
|
},
|
||||||
api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) =>
|
api::HostExtReq::FetchParsedConst(ref fpc @ api::FetchParsedConst(sys, id)) =>
|
||||||
with_ctx(get_ctx(sys).await, async move {
|
with_sys_record(sys, async {
|
||||||
let cnst = get_const(id).await;
|
let cnst = get_const(id).await;
|
||||||
hand.handle(fpc, &cnst.serialize().await).await
|
handle.reply(fpc, &serialize(cnst).await).await
|
||||||
})
|
})
|
||||||
.await,
|
.await,
|
||||||
api::HostExtReq::AtomReq(atom_req) => {
|
api::HostExtReq::AtomReq(atom_req) => {
|
||||||
let atom = atom_req.get_atom();
|
let atom = atom_req.get_atom();
|
||||||
let atom_req = atom_req.clone();
|
with_sys_record(atom.owner, async {
|
||||||
with_atom_record(&get_ctx, atom, async move |nfo, id, buf| {
|
let (nfo, id, buf) = resolve_atom_type(atom);
|
||||||
let actx = AtomCtx(buf, atom.drop);
|
let actx = AtomCtx(buf, atom.drop);
|
||||||
match &atom_req {
|
match &atom_req {
|
||||||
api::AtomReq::SerializeAtom(ser) => {
|
api::AtomReq::SerializeAtom(ser) => {
|
||||||
let mut buf = enc_vec(&id).await;
|
let mut buf = enc_vec(&id);
|
||||||
match nfo.serialize(actx, Pin::<&mut Vec<_>>::new(&mut buf)).await {
|
match nfo.serialize(actx, Pin::<&mut Vec<_>>::new(&mut buf)).await {
|
||||||
None => hand.handle(ser, &None).await,
|
None => handle.reply(ser, &None).await,
|
||||||
Some(refs) => {
|
Some(refs) => {
|
||||||
let refs =
|
let refs =
|
||||||
join_all(refs.into_iter().map(|ex| async { ex.into_api(&mut ()).await }))
|
join_all(refs.into_iter().map(async |ex| ex.into_api(&mut ()).await))
|
||||||
.await;
|
.await;
|
||||||
hand.handle(ser, &Some((buf, refs))).await
|
handle.reply(ser, &Some((buf, refs))).await
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) =>
|
api::AtomReq::AtomPrint(print @ api::AtomPrint(_)) =>
|
||||||
hand.handle(print, &nfo.print(actx).await.to_api()).await,
|
handle.reply(print, &nfo.print(actx).await.to_api()).await,
|
||||||
api::AtomReq::Fwded(fwded) => {
|
api::AtomReq::Fwded(fwded) => {
|
||||||
let api::Fwded(_, key, payload) = &fwded;
|
let api::Fwded(_, key, payload) = &fwded;
|
||||||
let mut reply = Vec::new();
|
let mut reply = Vec::new();
|
||||||
let key = Sym::from_api(*key, &interner).await;
|
let key = Sym::from_api(*key).await;
|
||||||
let some = nfo
|
let req = TrivialReqCycle { req: payload, rep: &mut reply };
|
||||||
.handle_req(
|
let some = nfo.handle_req_ref(actx, key, Box::new(req)).await;
|
||||||
actx,
|
handle.reply(fwded, &some.then_some(reply)).await
|
||||||
key,
|
|
||||||
Pin::<&mut &[u8]>::new(&mut &payload[..]),
|
|
||||||
Pin::<&mut Vec<_>>::new(&mut reply),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
hand.handle(fwded, &some.then_some(reply)).await
|
|
||||||
},
|
},
|
||||||
api::AtomReq::CallRef(call @ api::CallRef(_, arg)) => {
|
api::AtomReq::CallRef(call @ api::CallRef(_, arg)) => {
|
||||||
let expr_store = BorrowedExprStore::new();
|
let expr_store = BorrowedExprStore::new();
|
||||||
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
|
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
|
||||||
let ret = nfo.call_ref(actx, Expr::from_handle(expr_handle.clone())).await;
|
let ret = nfo.call_ref(actx, Expr::from_handle(expr_handle.clone())).await;
|
||||||
let api_expr = ret.serialize().await;
|
let api_expr = serialize(ret).await;
|
||||||
mem::drop(expr_handle);
|
mem::drop(expr_handle);
|
||||||
expr_store.dispose().await;
|
expr_store.dispose().await;
|
||||||
hand.handle(call, &api_expr).await
|
handle.reply(call, &api_expr).await
|
||||||
},
|
},
|
||||||
api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => {
|
api::AtomReq::FinalCall(call @ api::FinalCall(_, arg)) => {
|
||||||
let expr_store = BorrowedExprStore::new();
|
let expr_store = BorrowedExprStore::new();
|
||||||
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
|
let expr_handle = ExprHandle::borrowed(*arg, &expr_store);
|
||||||
let ret = nfo.call(actx, Expr::from_handle(expr_handle.clone())).await;
|
let ret = nfo.call(actx, Expr::from_handle(expr_handle.clone())).await;
|
||||||
let api_expr = ret.serialize().await;
|
let api_expr = serialize(ret).await;
|
||||||
mem::drop(expr_handle);
|
mem::drop(expr_handle);
|
||||||
expr_store.dispose().await;
|
expr_store.dispose().await;
|
||||||
hand.handle(call, &api_expr).await
|
handle.reply(call, &api_expr).await
|
||||||
},
|
|
||||||
api::AtomReq::Command(cmd @ api::Command(_)) => match nfo.command(actx).await {
|
|
||||||
Err(e) => hand.handle(cmd, &Err(e.to_api())).await,
|
|
||||||
Ok(opt) => match opt {
|
|
||||||
None => hand.handle(cmd, &Ok(api::NextStep::Halt)).await,
|
|
||||||
Some(cont) => {
|
|
||||||
let cont = cont.serialize().await;
|
|
||||||
hand.handle(cmd, &Ok(api::NextStep::Continue(cont))).await
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -383,41 +400,51 @@ pub fn extension_init(
|
|||||||
},
|
},
|
||||||
api::HostExtReq::DeserAtom(deser) => {
|
api::HostExtReq::DeserAtom(deser) => {
|
||||||
let api::DeserAtom(sys, buf, refs) = &deser;
|
let api::DeserAtom(sys, buf, refs) = &deser;
|
||||||
let mut read = &mut &buf[..];
|
let read = &mut &buf[..];
|
||||||
let ctx = get_ctx(*sys).await;
|
with_sys_record(*sys, async {
|
||||||
// SAFETY: deserialization implicitly grants ownership to previously owned exprs
|
// SAFETY: deserialization implicitly grants ownership to previously owned exprs
|
||||||
let refs = (refs.iter())
|
let refs = (refs.iter())
|
||||||
.map(|tk| Expr::from_handle(ExprHandle::deserialize(*tk)))
|
.map(|tk| Expr::from_handle(ExprHandle::deserialize(*tk)))
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
let id = AtomTypeId::decode(Pin::new(&mut read)).await;
|
let id = AtomTypeId::decode_slice(read);
|
||||||
let inst = ctx.cted().inst();
|
let nfo = (dyn_cted().inst().card().ops_by_atid(id))
|
||||||
let nfo = atom_by_idx(inst.card(), id).expect("Deserializing atom with invalid ID");
|
.expect("Deserializing atom with invalid ID");
|
||||||
hand.handle(&deser, &nfo.deserialize(read, &refs).await).await
|
handle.reply(&deser, &nfo.deserialize(read, &refs).await).await
|
||||||
|
})
|
||||||
|
.await
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
.boxed_local()
|
.await
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
*interner_cell.borrow_mut() =
|
// add essential services to the very tail, then fold all context into the run
|
||||||
Some(Interner::new_replica(rn.clone().map(|ir: api::IntReq| ir.into_root())));
|
// future
|
||||||
spawner(Box::pin(clone!(spawner; async move {
|
SYSTEM_TABLE
|
||||||
let mut streams = stream_select! { in_recv.map(Some), exit_recv.map(|_| None) };
|
.scope(
|
||||||
while let Some(item) = streams.next().await {
|
RefCell::default(),
|
||||||
match item {
|
with_interner(
|
||||||
Some(rcvd) => spawner(Box::pin(clone!(rn; async move { rn.receive(&rcvd[..]).await }))),
|
new_interner(),
|
||||||
None => break,
|
with_logger(
|
||||||
}
|
logger2,
|
||||||
}
|
with_comm(
|
||||||
})));
|
Rc::new(client),
|
||||||
ExtInit {
|
comm_ctx,
|
||||||
header: ext_header,
|
SPAWN.scope(
|
||||||
port: Box::new(ExtensionOwner {
|
Rc::new(move |delay, fut| {
|
||||||
out_recv: Mutex::new(out_recv),
|
let (poll, handle) = to_task(fut);
|
||||||
out_send,
|
spawn(delay, Box::pin(poll));
|
||||||
_interner_cell: interner_cell,
|
Box::new(handle)
|
||||||
_systems_lock: systems_lock,
|
|
||||||
}),
|
}),
|
||||||
|
(self.context.into_iter()).fold(
|
||||||
|
Box::pin(async { extension_fut.await.unwrap() }) as LocalBoxFuture<()>,
|
||||||
|
|fut, cx| cx.apply(fut),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,310 +0,0 @@
|
|||||||
use std::any::Any;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::sync::{Arc, OnceLock};
|
|
||||||
use std::{fmt, iter};
|
|
||||||
|
|
||||||
use dyn_clone::{clone_box, DynClone};
|
|
||||||
use itertools::Itertools;
|
|
||||||
use orchid_base::boxed_iter::{box_once, BoxedIter};
|
|
||||||
use orchid_base::clone;
|
|
||||||
use orchid_base::error::{ErrPos, OrcError};
|
|
||||||
use orchid_base::interner::{deintern, intern};
|
|
||||||
use orchid_base::location::{GetSrc, Pos};
|
|
||||||
use orchid_base::reqnot::{ReqNot, Requester};
|
|
||||||
|
|
||||||
use crate::api;
|
|
||||||
|
|
||||||
/// Errors addressed to the developer which are to be resolved with
|
|
||||||
/// code changes
|
|
||||||
pub trait ProjectError: Sized + Send + Sync + 'static {
|
|
||||||
/// A general description of this type of error
|
|
||||||
const DESCRIPTION: &'static str;
|
|
||||||
/// A formatted message that includes specific parameters
|
|
||||||
#[must_use]
|
|
||||||
fn message(&self) -> String { self.description().to_string() }
|
|
||||||
/// Code positions relevant to this error. If you don't implement this, you
|
|
||||||
/// must implement [ProjectError::one_position]
|
|
||||||
#[must_use]
|
|
||||||
fn positions(&self) -> impl IntoIterator<Item = ErrPos> + '_ {
|
|
||||||
box_once(ErrPos { position: self.one_position(), message: None })
|
|
||||||
}
|
|
||||||
/// Short way to provide a single origin. If you don't implement this, you
|
|
||||||
/// must implement [ProjectError::positions]
|
|
||||||
#[must_use]
|
|
||||||
fn one_position(&self) -> Pos {
|
|
||||||
unimplemented!("Error type did not implement either positions or one_position")
|
|
||||||
}
|
|
||||||
/// Convert the error into an `Arc<dyn DynProjectError>` to be able to
|
|
||||||
/// handle various errors together
|
|
||||||
#[must_use]
|
|
||||||
fn pack(self) -> ProjectErrorObj { Arc::new(self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Object-safe version of [ProjectError]. Implement that instead of this.
|
|
||||||
pub trait DynProjectError: Send + Sync + 'static {
|
|
||||||
/// Access type information about this error
|
|
||||||
#[must_use]
|
|
||||||
fn as_any_ref(&self) -> &dyn Any;
|
|
||||||
/// Pack the error into a trait object, or leave it as-is if it's already a
|
|
||||||
/// trait object
|
|
||||||
#[must_use]
|
|
||||||
fn into_packed(self: Arc<Self>) -> ProjectErrorObj;
|
|
||||||
/// A general description of this type of error
|
|
||||||
#[must_use]
|
|
||||||
fn description(&self) -> Cow<'_, str>;
|
|
||||||
/// A formatted message that includes specific parameters
|
|
||||||
#[must_use]
|
|
||||||
fn message(&self) -> String { self.description().to_string() }
|
|
||||||
/// Code positions relevant to this error.
|
|
||||||
#[must_use]
|
|
||||||
fn positions(&self) -> BoxedIter<'_, ErrPos>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> DynProjectError for T
|
|
||||||
where T: ProjectError
|
|
||||||
{
|
|
||||||
fn as_any_ref(&self) -> &dyn Any { self }
|
|
||||||
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { self }
|
|
||||||
fn description(&self) -> Cow<'_, str> { Cow::Borrowed(T::DESCRIPTION) }
|
|
||||||
fn message(&self) -> String { ProjectError::message(self) }
|
|
||||||
fn positions(&self) -> BoxedIter<ErrPos> { Box::new(ProjectError::positions(self).into_iter()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pretty_print(err: &dyn DynProjectError, get_src: &mut impl GetSrc) -> String {
|
|
||||||
let description = err.description();
|
|
||||||
let message = err.message();
|
|
||||||
let positions = err.positions().collect::<Vec<_>>();
|
|
||||||
let head = format!("Project error: {description}\n{message}");
|
|
||||||
if positions.is_empty() {
|
|
||||||
head + "No origins specified"
|
|
||||||
} else {
|
|
||||||
iter::once(head)
|
|
||||||
.chain(positions.iter().map(|ErrPos { position: origin, message }| match message {
|
|
||||||
None => format!("@{}", origin.pretty_print(get_src)),
|
|
||||||
Some(msg) => format!("@{}: {msg}", origin.pretty_print(get_src)),
|
|
||||||
}))
|
|
||||||
.join("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DynProjectError for ProjectErrorObj {
|
|
||||||
fn as_any_ref(&self) -> &dyn Any { (**self).as_any_ref() }
|
|
||||||
fn description(&self) -> Cow<'_, str> { (**self).description() }
|
|
||||||
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { (*self).clone() }
|
|
||||||
fn message(&self) -> String { (**self).message() }
|
|
||||||
fn positions(&self) -> BoxedIter<'_, ErrPos> { (**self).positions() }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type-erased [ProjectError] implementor through the [DynProjectError]
|
|
||||||
/// object-trait
|
|
||||||
pub type ProjectErrorObj = Arc<dyn DynProjectError>;
|
|
||||||
/// Alias for a result with an error of [ProjectErrorObj]. This is the type of
|
|
||||||
/// result most commonly returned by pre-runtime operations.
|
|
||||||
pub type ProjectResult<T> = Result<T, ProjectErrorObj>;
|
|
||||||
|
|
||||||
/// A trait for error types that are only missing an origin. Do not depend on
|
|
||||||
/// this trait, refer to [DynErrorSansOrigin] instead.
|
|
||||||
pub trait ErrorSansOrigin: Clone + Sized + Send + Sync + 'static {
|
|
||||||
/// General description of the error condition
|
|
||||||
const DESCRIPTION: &'static str;
|
|
||||||
/// Specific description of the error including code fragments or concrete
|
|
||||||
/// data if possible
|
|
||||||
fn message(&self) -> String { Self::DESCRIPTION.to_string() }
|
|
||||||
/// Convert the error to a type-erased structure for handling on shared
|
|
||||||
/// channels
|
|
||||||
fn pack(self) -> ErrorSansOriginObj { Box::new(self) }
|
|
||||||
/// A shortcut to streamline switching code between [ErrorSansOriginObj] and
|
|
||||||
/// concrete types
|
|
||||||
fn bundle(self, origin: &Pos) -> ProjectErrorObj { self.pack().bundle(origin) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Object-safe equivalent to [ErrorSansOrigin]. Implement that one instead of
|
|
||||||
/// this. Typically found as [ErrorSansOriginObj]
|
|
||||||
pub trait DynErrorSansOrigin: Any + Send + Sync + DynClone {
|
|
||||||
/// Allow to downcast the base object to distinguish between various errors.
|
|
||||||
/// The main intended purpose is to trigger a fallback when [CodeNotFound] is
|
|
||||||
/// encountered, but the possibilities are not limited to that.
|
|
||||||
fn as_any_ref(&self) -> &dyn Any;
|
|
||||||
/// Regularize the type
|
|
||||||
fn into_packed(self: Box<Self>) -> ErrorSansOriginObj;
|
|
||||||
/// Generic description of the error condition
|
|
||||||
fn description(&self) -> Cow<'_, str>;
|
|
||||||
/// Specific description of this particular error
|
|
||||||
fn message(&self) -> String;
|
|
||||||
/// Add an origin
|
|
||||||
fn bundle(self: Box<Self>, origin: &Pos) -> ProjectErrorObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Type-erased [ErrorSansOrigin] implementor through the object-trait
|
|
||||||
/// [DynErrorSansOrigin]. This can be turned into a [ProjectErrorObj] with
|
|
||||||
/// [ErrorSansOriginObj::bundle].
|
|
||||||
pub type ErrorSansOriginObj = Box<dyn DynErrorSansOrigin>;
|
|
||||||
/// A generic project result without origin
|
|
||||||
pub type ResultSansOrigin<T> = Result<T, ErrorSansOriginObj>;
|
|
||||||
|
|
||||||
impl<T: ErrorSansOrigin + 'static> DynErrorSansOrigin for T {
|
|
||||||
fn description(&self) -> Cow<'_, str> { Cow::Borrowed(Self::DESCRIPTION) }
|
|
||||||
fn message(&self) -> String { (*self).message() }
|
|
||||||
fn as_any_ref(&self) -> &dyn Any { self }
|
|
||||||
fn into_packed(self: Box<Self>) -> ErrorSansOriginObj { (*self).pack() }
|
|
||||||
fn bundle(self: Box<Self>, origin: &Pos) -> ProjectErrorObj {
|
|
||||||
Arc::new(OriginBundle(origin.clone(), *self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Clone for ErrorSansOriginObj {
|
|
||||||
fn clone(&self) -> Self { clone_box(&**self) }
|
|
||||||
}
|
|
||||||
impl DynErrorSansOrigin for ErrorSansOriginObj {
|
|
||||||
fn description(&self) -> Cow<'_, str> { (**self).description() }
|
|
||||||
fn message(&self) -> String { (**self).message() }
|
|
||||||
fn as_any_ref(&self) -> &dyn Any { (**self).as_any_ref() }
|
|
||||||
fn into_packed(self: Box<Self>) -> ErrorSansOriginObj { *self }
|
|
||||||
fn bundle(self: Box<Self>, origin: &Pos) -> ProjectErrorObj { (*self).bundle(origin) }
|
|
||||||
}
|
|
||||||
impl fmt::Display for ErrorSansOriginObj {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
writeln!(f, "{}\nOrigin missing from error", self.message())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl fmt::Debug for ErrorSansOriginObj {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self}") }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct OriginBundle<T: ErrorSansOrigin>(Pos, T);
|
|
||||||
impl<T: ErrorSansOrigin> DynProjectError for OriginBundle<T> {
|
|
||||||
fn as_any_ref(&self) -> &dyn Any { self.1.as_any_ref() }
|
|
||||||
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { self }
|
|
||||||
fn description(&self) -> Cow<'_, str> { self.1.description() }
|
|
||||||
fn message(&self) -> String { self.1.message() }
|
|
||||||
fn positions(&self) -> BoxedIter<ErrPos> {
|
|
||||||
box_once(ErrPos { position: self.0.clone(), message: None })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A collection for tracking fatal errors without halting. Participating
|
|
||||||
/// functions return [ProjectResult] even if they only ever construct [Ok]. When
|
|
||||||
/// they call other participating functions, instead of directly forwarding
|
|
||||||
/// errors with `?` they should prefer constructing a fallback value with
|
|
||||||
/// [Reporter::fallback]. If any error is added to a [Reporter] in a function,
|
|
||||||
/// the return value is valid but its meaning need not be related in any way to
|
|
||||||
/// the inputs.
|
|
||||||
///
|
|
||||||
/// Returning [Err] from a function that accepts `&mut Reporter` indicates not
|
|
||||||
/// that there was a fatal error but that it wasn't possible to construct a
|
|
||||||
/// fallback, so if it can, the caller should construct one.
|
|
||||||
pub struct Reporter(RefCell<Vec<ProjectErrorObj>>);
|
|
||||||
impl Reporter {
|
|
||||||
/// Create a new error reporter
|
|
||||||
pub fn new() -> Self { Self(RefCell::new(Vec::new())) }
|
|
||||||
/// Returns true if any errors were regorded. If this ever returns true, it
|
|
||||||
/// will always return true in the future.
|
|
||||||
pub fn failing(&self) -> bool { !self.0.borrow().is_empty() }
|
|
||||||
/// Report a fatal error
|
|
||||||
pub fn report(&self, error: ProjectErrorObj) {
|
|
||||||
match error.as_any_ref().downcast_ref::<MultiError>() {
|
|
||||||
None => self.0.borrow_mut().push(error),
|
|
||||||
Some(me) =>
|
|
||||||
for err in me.0.iter() {
|
|
||||||
self.report(err.clone())
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Catch a fatal error, report it, and substitute the value
|
|
||||||
pub fn fallback<T>(&self, res: ProjectResult<T>, cb: impl FnOnce(ProjectErrorObj) -> T) -> T {
|
|
||||||
res.inspect_err(|e| self.report(e.clone())).unwrap_or_else(cb)
|
|
||||||
}
|
|
||||||
/// Take the errors out of the reporter
|
|
||||||
#[must_use]
|
|
||||||
pub fn into_errors(self) -> Option<Vec<ProjectErrorObj>> {
|
|
||||||
let v = self.0.into_inner();
|
|
||||||
if v.is_empty() { None } else { Some(v) }
|
|
||||||
}
|
|
||||||
/// Raise an error if the reporter contains any errors
|
|
||||||
pub fn bind(self) -> ProjectResult<()> {
|
|
||||||
match self.into_errors() {
|
|
||||||
None => Ok(()),
|
|
||||||
Some(v) if v.len() == 1 => Err(v.into_iter().next().unwrap()),
|
|
||||||
Some(v) => Err(MultiError(v).pack()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Reporter {
|
|
||||||
fn default() -> Self { Self::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unpack_into(err: impl DynProjectError, res: &mut Vec<ProjectErrorObj>) {
|
|
||||||
match err.as_any_ref().downcast_ref::<MultiError>() {
|
|
||||||
Some(multi) => multi.0.iter().for_each(|e| unpack_into(e.clone(), res)),
|
|
||||||
None => res.push(Arc::new(err).into_packed()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unpack_err(err: ProjectErrorObj) -> Vec<ProjectErrorObj> {
|
|
||||||
let mut out = Vec::new();
|
|
||||||
unpack_into(err, &mut out);
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pack_err<E: DynProjectError>(iter: impl IntoIterator<Item = E>) -> ProjectErrorObj {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
iter.into_iter().for_each(|e| unpack_into(e, &mut errors));
|
|
||||||
if errors.len() == 1 { errors.into_iter().next().unwrap() } else { MultiError(errors).pack() }
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MultiError(Vec<ProjectErrorObj>);
|
|
||||||
impl ProjectError for MultiError {
|
|
||||||
const DESCRIPTION: &'static str = "Multiple errors occurred";
|
|
||||||
fn message(&self) -> String { format!("{} errors occurred", self.0.len()) }
|
|
||||||
fn positions(&self) -> impl IntoIterator<Item = ErrPos> + '_ {
|
|
||||||
self.0.iter().flat_map(|e| {
|
|
||||||
e.positions().map(|pos| {
|
|
||||||
let emsg = e.message();
|
|
||||||
let msg = match pos.message {
|
|
||||||
None => emsg,
|
|
||||||
Some(s) if s.is_empty() => emsg,
|
|
||||||
Some(pmsg) => format!("{emsg}: {pmsg}"),
|
|
||||||
};
|
|
||||||
ErrPos { position: pos.position, message: Some(Arc::new(msg)) }
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn err_to_api(err: ProjectErrorObj) -> api::OrcErr {
|
|
||||||
api::OrcErr {
|
|
||||||
description: intern(&*err.description()).marker(),
|
|
||||||
message: Arc::new(err.message()),
|
|
||||||
locations: err.positions().map(|e| e.to_api()).collect_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RelayedError {
|
|
||||||
pub id: Option<api::ErrId>,
|
|
||||||
pub reqnot: ReqNot<api::ExtMsgSet>,
|
|
||||||
pub details: OnceLock<OrcError>,
|
|
||||||
}
|
|
||||||
impl RelayedError {
|
|
||||||
fn details(&self) -> &OrcError {
|
|
||||||
let Self { id, reqnot, details: data } = self;
|
|
||||||
data.get_or_init(clone!(reqnot; move || {
|
|
||||||
let id = id.expect("Either data or ID must be initialized");
|
|
||||||
let projerr = reqnot.request(api::GetErrorDetails(id));
|
|
||||||
OrcError {
|
|
||||||
description: deintern(projerr.description),
|
|
||||||
message: projerr.message,
|
|
||||||
positions: projerr.locations.iter().map(ErrPos::from_api).collect_vec(),
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl DynProjectError for RelayedError {
|
|
||||||
fn description(&self) -> Cow<'_, str> { Cow::Borrowed(self.details().description.as_str()) }
|
|
||||||
fn message(&self) -> String { self.details().message.to_string() }
|
|
||||||
fn as_any_ref(&self) -> &dyn std::any::Any { self }
|
|
||||||
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { self }
|
|
||||||
fn positions(&self) -> BoxedIter<'_, ErrPos> {
|
|
||||||
Box::new(self.details().positions.iter().cloned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,39 +1,47 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hash::Hash;
|
use std::hash::{Hash, Hasher};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::thread::panicking;
|
||||||
|
|
||||||
use async_once_cell::OnceCell;
|
use async_once_cell::OnceCell;
|
||||||
use derive_destructure::destructure;
|
use derive_destructure::destructure;
|
||||||
|
use futures::future::join_all;
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use orchid_base::error::OrcErrv;
|
use orchid_base::{FmtCtx, FmtUnit, Format, OrcErrv, Pos, stash};
|
||||||
use orchid_base::format::{FmtCtx, FmtUnit, Format};
|
|
||||||
use orchid_base::location::Pos;
|
|
||||||
use orchid_base::reqnot::Requester;
|
|
||||||
|
|
||||||
use crate::api;
|
use crate::gen_expr::{GExpr, slot};
|
||||||
use crate::atom::ForeignAtom;
|
use crate::{ForeignAtom, api, notify, request, sys_id};
|
||||||
use crate::context::{ctx, i};
|
|
||||||
use crate::gen_expr::{GExpr, GExprKind};
|
|
||||||
|
|
||||||
|
/// Handle for a lifetime associated with an [ExprHandle], such as a function
|
||||||
|
/// call. Can be passed into [ExprHandle::borrowed] as an optimization over
|
||||||
|
/// [ExprHandle::from_ticket]
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// The [Drop] of this type panics by default unless the stack is already
|
||||||
|
/// unwinding. You need to make sure you dispose of it by calling
|
||||||
|
/// [Self::dispose].
|
||||||
pub struct BorrowedExprStore(RefCell<Option<HashSet<Rc<ExprHandle>>>>);
|
pub struct BorrowedExprStore(RefCell<Option<HashSet<Rc<ExprHandle>>>>);
|
||||||
impl BorrowedExprStore {
|
impl BorrowedExprStore {
|
||||||
pub(crate) fn new() -> Self { Self(RefCell::new(Some(HashSet::new()))) }
|
pub(crate) fn new() -> Self { Self(RefCell::new(Some(HashSet::new()))) }
|
||||||
pub async fn dispose(self) {
|
pub async fn dispose(self) {
|
||||||
let elements = self.0.borrow_mut().take().unwrap();
|
let set = self.0.borrow_mut().take().expect("Double dispose of BorrowedExprStore!");
|
||||||
for handle in elements {
|
join_all(set.into_iter().map(ExprHandle::on_borrow_expire)).await;
|
||||||
handle.on_borrow_expire().await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for BorrowedExprStore {
|
impl Drop for BorrowedExprStore {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.0.borrow().is_some() {
|
if self.0.borrow().is_some() && !panicking() {
|
||||||
panic!("This should always be explicitly disposed")
|
panic!("This should always be explicitly disposed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A RAII wrapper over an [api::ExprTicket]. Extension authors usually use
|
||||||
|
/// [Expr] for all purposes as this type does not deal with the details of the
|
||||||
|
/// expression associated with the ticket, it merely ensures that [api::Acquire]
|
||||||
|
/// and [api::Release] are sent at appropriate times.
|
||||||
#[derive(destructure, PartialEq, Eq, Hash)]
|
#[derive(destructure, PartialEq, Eq, Hash)]
|
||||||
pub struct ExprHandle(api::ExprTicket);
|
pub struct ExprHandle(api::ExprTicket);
|
||||||
impl ExprHandle {
|
impl ExprHandle {
|
||||||
@@ -72,8 +80,8 @@ impl ExprHandle {
|
|||||||
/// to lend the expr, and you expect the receiver to use
|
/// to lend the expr, and you expect the receiver to use
|
||||||
/// [ExprHandle::borrowed] or [ExprHandle::from_ticket]
|
/// [ExprHandle::borrowed] or [ExprHandle::from_ticket]
|
||||||
pub fn ticket(&self) -> api::ExprTicket { self.0 }
|
pub fn ticket(&self) -> api::ExprTicket { self.0 }
|
||||||
async fn send_acq(&self) { ctx().reqnot().notify(api::Acquire(ctx().sys_id(), self.0)).await }
|
async fn send_acq(&self) { notify(api::Acquire(sys_id(), self.0)).await }
|
||||||
/// If this is the last one reference, do nothing, otherwise send an Acquire
|
/// If this is the last reference, do nothing, otherwise send an Acquire
|
||||||
pub async fn on_borrow_expire(self: Rc<Self>) { self.serialize().await; }
|
pub async fn on_borrow_expire(self: Rc<Self>) { self.serialize().await; }
|
||||||
/// Drop the handle and get the ticket without a release notification.
|
/// Drop the handle and get the ticket without a release notification.
|
||||||
/// Use this with messages that imply ownership transfer. This function is
|
/// Use this with messages that imply ownership transfer. This function is
|
||||||
@@ -93,18 +101,25 @@ impl fmt::Debug for ExprHandle {
|
|||||||
}
|
}
|
||||||
impl Drop for ExprHandle {
|
impl Drop for ExprHandle {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let notif = api::Release(ctx().sys_id(), self.0);
|
let notif = api::Release(sys_id(), self.0);
|
||||||
ctx().spawn(async move { ctx().reqnot().clone().notify(notif).await })
|
stash(async move { notify(notif).await })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A smart object that keeps an expression alive in the interpreter until all
|
||||||
|
/// references are dropped and provides information about that expression. These
|
||||||
|
/// can be stored in any pattern, but care must be taken as adding new ones into
|
||||||
|
/// a structure that is already visible to the interpreter can easily cause a
|
||||||
|
/// memory leak.
|
||||||
#[derive(Clone, Debug, destructure)]
|
#[derive(Clone, Debug, destructure)]
|
||||||
pub struct Expr {
|
pub struct Expr {
|
||||||
handle: Rc<ExprHandle>,
|
handle: Rc<ExprHandle>,
|
||||||
data: Rc<OnceCell<ExprData>>,
|
data: Rc<OnceCell<ExprData>>,
|
||||||
}
|
}
|
||||||
impl Expr {
|
impl Expr {
|
||||||
|
/// Wrap a handle in order to retrieve details about it
|
||||||
pub fn from_handle(handle: Rc<ExprHandle>) -> Self { Self { handle, data: Rc::default() } }
|
pub fn from_handle(handle: Rc<ExprHandle>) -> Self { Self { handle, data: Rc::default() } }
|
||||||
|
/// Wrap a handle the details of which are already known
|
||||||
pub fn from_data(handle: Rc<ExprHandle>, d: ExprData) -> Self {
|
pub fn from_data(handle: Rc<ExprHandle>, d: ExprData) -> Self {
|
||||||
Self { handle, data: Rc::new(OnceCell::from(d)) }
|
Self { handle, data: Rc::new(OnceCell::from(d)) }
|
||||||
}
|
}
|
||||||
@@ -114,31 +129,36 @@ impl Expr {
|
|||||||
pub async fn deserialize(tk: api::ExprTicket) -> Self {
|
pub async fn deserialize(tk: api::ExprTicket) -> Self {
|
||||||
Self::from_handle(ExprHandle::deserialize(tk))
|
Self::from_handle(ExprHandle::deserialize(tk))
|
||||||
}
|
}
|
||||||
|
/// Fetch the details of this expression via [api::Inspect] if not already
|
||||||
|
/// known, and return them
|
||||||
pub async fn data(&self) -> &ExprData {
|
pub async fn data(&self) -> &ExprData {
|
||||||
(self.data.get_or_init(async {
|
(self.data.get_or_init(async {
|
||||||
let details = ctx().reqnot().request(api::Inspect { target: self.handle.ticket() }).await;
|
let details = request(api::Inspect { target: self.handle.ticket() }).await;
|
||||||
let pos = Pos::from_api(&details.location, &i()).await;
|
let pos = Pos::from_api(&details.location).await;
|
||||||
let kind = match details.kind {
|
let kind = match details.kind {
|
||||||
api::InspectedKind::Atom(a) =>
|
api::InspectedKind::Atom(a) =>
|
||||||
ExprKind::Atom(ForeignAtom::new(self.handle.clone(), a, pos.clone())),
|
ExprKind::Atom(ForeignAtom::new(self.handle.clone(), a, pos.clone())),
|
||||||
api::InspectedKind::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(&b, &i()).await),
|
api::InspectedKind::Bottom(b) => ExprKind::Bottom(OrcErrv::from_api(b).await),
|
||||||
api::InspectedKind::Opaque => ExprKind::Opaque,
|
api::InspectedKind::Opaque => ExprKind::Opaque,
|
||||||
};
|
};
|
||||||
ExprData { pos, kind }
|
ExprData { pos, kind }
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
/// Attempt to downcast this to a [ForeignAtom]
|
||||||
pub async fn atom(self) -> Result<ForeignAtom, Self> {
|
pub async fn atom(self) -> Result<ForeignAtom, Self> {
|
||||||
match self.data().await {
|
match self.data().await {
|
||||||
ExprData { kind: ExprKind::Atom(atom), .. } => Ok(atom.clone()),
|
ExprData { kind: ExprKind::Atom(atom), .. } => Ok(atom.clone()),
|
||||||
_ => Err(self),
|
_ => Err(self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Obtain code location info associated with this expression for logging.
|
||||||
|
pub async fn pos(&self) -> Pos { self.data().await.pos.clone() }
|
||||||
|
/// Clone out the handle for this expression
|
||||||
pub fn handle(&self) -> Rc<ExprHandle> { self.handle.clone() }
|
pub fn handle(&self) -> Rc<ExprHandle> { self.handle.clone() }
|
||||||
|
/// Wrap this expression in a [GExpr] synchronously as an escape hatch.
|
||||||
pub fn slot(&self) -> GExpr {
|
/// Otherwise identical to this type's [crate::ToExpr] impl
|
||||||
GExpr { pos: Pos::SlotTarget, kind: GExprKind::Slot(self.clone()) }
|
pub fn slot(&self) -> GExpr { slot(self.clone()) }
|
||||||
}
|
|
||||||
/// Increments the refcount to ensure that the ticket remains valid even if
|
/// Increments the refcount to ensure that the ticket remains valid even if
|
||||||
/// the handle is freed. To avoid a leak, [Expr::deserialize] must eventually
|
/// the handle is freed. To avoid a leak, [Expr::deserialize] must eventually
|
||||||
/// be called.
|
/// be called.
|
||||||
@@ -146,24 +166,34 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
impl Format for Expr {
|
impl Format for Expr {
|
||||||
async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
async fn print<'a>(&'a self, _c: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||||
match &self.data().await.kind {
|
FmtUnit::from_api(&request(api::ExprPrint { target: self.handle.0 }).await)
|
||||||
ExprKind::Opaque => "OPAQUE".to_string().into(),
|
|
||||||
ExprKind::Bottom(b) => format!("Bottom({b})").into(),
|
|
||||||
ExprKind::Atom(a) =>
|
|
||||||
FmtUnit::from_api(&ctx().reqnot().request(api::ExtAtomPrint(a.atom.clone())).await),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Eq for Expr {}
|
||||||
|
impl PartialEq for Expr {
|
||||||
|
fn eq(&self, other: &Self) -> bool { self.handle == other.handle }
|
||||||
|
}
|
||||||
|
impl Hash for Expr {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) { self.handle.hash(state); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about an expression
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ExprData {
|
pub struct ExprData {
|
||||||
|
/// Source code location data associated with the expression for debug logging
|
||||||
pub pos: Pos,
|
pub pos: Pos,
|
||||||
|
/// Limited information on the value available to extensions
|
||||||
pub kind: ExprKind,
|
pub kind: ExprKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// All that is visible about a runtime value to extensions. This
|
||||||
|
/// information is limited for the sake of extensibility
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ExprKind {
|
pub enum ExprKind {
|
||||||
|
/// An atom, local or foreign
|
||||||
Atom(ForeignAtom),
|
Atom(ForeignAtom),
|
||||||
|
/// A runtime error
|
||||||
Bottom(OrcErrv),
|
Bottom(OrcErrv),
|
||||||
|
/// Some other value, possibly normalizes to one of the above
|
||||||
Opaque,
|
Opaque,
|
||||||
}
|
}
|
||||||
|
|||||||
13
orchid-extension/src/ext_port.rs
Normal file
13
orchid-extension/src/ext_port.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use std::pin::Pin;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use futures::future::LocalBoxFuture;
|
||||||
|
use futures::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
pub struct ExtPort {
|
||||||
|
pub input: Pin<Box<dyn AsyncRead>>,
|
||||||
|
pub output: Pin<Box<dyn AsyncWrite>>,
|
||||||
|
pub log: Pin<Box<dyn AsyncWrite>>,
|
||||||
|
pub spawn: Rc<dyn Fn(Duration, LocalBoxFuture<'static, ()>)>,
|
||||||
|
}
|
||||||
@@ -1,42 +1,76 @@
|
|||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use futures::future::LocalBoxFuture;
|
use futures::future::{LocalBoxFuture, join_all};
|
||||||
use futures::lock::Mutex;
|
|
||||||
use futures::{AsyncWrite, FutureExt};
|
use futures::{AsyncWrite, FutureExt};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use never::Never;
|
use never::Never;
|
||||||
use orchid_api_traits::Encode;
|
use orchid_api_traits::Encode;
|
||||||
use orchid_base::clone;
|
use orchid_base::{FmtCtx, FmtUnit, OrcRes, Pos, Sym, clone};
|
||||||
use orchid_base::error::OrcRes;
|
use task_local::task_local;
|
||||||
use orchid_base::format::{FmtCtx, FmtUnit};
|
|
||||||
use orchid_base::name::Sym;
|
|
||||||
use trait_set::trait_set;
|
use trait_set::trait_set;
|
||||||
|
|
||||||
use crate::atom::Atomic;
|
use crate::gen_expr::{GExpr, new_atom};
|
||||||
use crate::atom_owned::{DeserializeCtx, OwnedAtom, OwnedVariant};
|
use crate::{
|
||||||
use crate::context::{SysCtxEntry, ctx, i};
|
Atomic, DeserializeCtx, ExecHandle, Expr, OwnedAtom, OwnedVariant, ToExpr, api, exec, sys_id,
|
||||||
use crate::conv::ToExpr;
|
};
|
||||||
use crate::coroutine_exec::{ExecHandle, exec};
|
|
||||||
use crate::expr::Expr;
|
|
||||||
use crate::gen_expr::GExpr;
|
|
||||||
|
|
||||||
trait_set! {
|
trait_set! {
|
||||||
trait FunCB = Fn(Vec<Expr>) -> LocalBoxFuture<'static, OrcRes<GExpr>> + 'static;
|
trait FunCB = Fn(Vec<Expr>) -> LocalBoxFuture<'static, OrcRes<GExpr>> + 'static;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task_local! {
|
||||||
|
static ARGV: Vec<Expr>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wihtin an [ExprFunc]'s body, access a raw argument by index
|
||||||
|
pub fn get_arg(idx: usize) -> Expr {
|
||||||
|
ARGV
|
||||||
|
.try_with(|argv| {
|
||||||
|
(argv.get(idx).cloned())
|
||||||
|
.unwrap_or_else(|| panic!("Cannot read argument ##{idx}, only have {}", argv.len()))
|
||||||
|
})
|
||||||
|
.expect("get_arg called outside ExprFunc")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the number of arguments accepted by this [ExprFunc]
|
||||||
|
pub fn get_argc() -> usize {
|
||||||
|
ARGV.try_with(|argv| argv.len()).expect("get_arg called outside ExprFunc")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the code locations associated with specific arguments by index.
|
||||||
|
/// This is intended to be the last argument to [orchid_base::mk_errv]
|
||||||
|
pub async fn get_arg_posv(idxes: impl IntoIterator<Item = usize>) -> impl Iterator<Item = Pos> {
|
||||||
|
let args = (ARGV.try_with(|argv| idxes.into_iter().map(|i| &argv[i]).cloned().collect_vec()))
|
||||||
|
.expect("get_arg_posv called outside ExprFunc");
|
||||||
|
join_all(args.iter().map(|expr| expr.pos())).await.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Rust lambda that can be placed into an Orchid atom. This requires that
|
||||||
|
///
|
||||||
|
/// - the lambda is [Clone] and `'static`
|
||||||
|
/// - All of its arguments are [crate::TryFromExpr]
|
||||||
|
/// - Its return value is [crate::ToExpr]
|
||||||
|
/// - For the sake of compilation time, at present the trait is only implemented
|
||||||
|
/// for up to 6 arguments
|
||||||
pub trait ExprFunc<I, O>: Clone + 'static {
|
pub trait ExprFunc<I, O>: Clone + 'static {
|
||||||
fn argtyps() -> &'static [TypeId];
|
fn argtyps() -> &'static [TypeId];
|
||||||
fn apply<'a>(&self, hand: ExecHandle<'a>, v: Vec<Expr>) -> impl Future<Output = OrcRes<GExpr>>;
|
fn apply<'a>(&self, hand: ExecHandle<'a>, v: Vec<Expr>) -> impl Future<Output = OrcRes<GExpr>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
task_local! {
|
||||||
struct FunsCtx(Mutex<HashMap<Sym, FunRecord>>);
|
static FUNS_CTX: RefCell<HashMap<(api::SysId, Sym), FunRecord>>;
|
||||||
impl SysCtxEntry for FunsCtx {}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_funs_ctx<'a>(fut: LocalBoxFuture<'a, ()>) -> LocalBoxFuture<'a, ()> {
|
||||||
|
Box::pin(FUNS_CTX.scope(RefCell::default(), fut))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct FunRecord {
|
struct FunRecord {
|
||||||
argtyps: &'static [TypeId],
|
argtyps: &'static [TypeId],
|
||||||
@@ -50,7 +84,9 @@ fn process_args<I, O, F: ExprFunc<I, O>>(f: F) -> FunRecord {
|
|||||||
exec(async move |mut hand| {
|
exec(async move |mut hand| {
|
||||||
let mut norm_args = Vec::with_capacity(v.len());
|
let mut norm_args = Vec::with_capacity(v.len());
|
||||||
for (expr, typ) in v.into_iter().zip(argtyps) {
|
for (expr, typ) in v.into_iter().zip(argtyps) {
|
||||||
if *typ != TypeId::of::<Expr>() {
|
if *typ == TypeId::of::<Expr>() {
|
||||||
|
norm_args.push(expr);
|
||||||
|
} else {
|
||||||
norm_args.push(hand.exec(expr).await?);
|
norm_args.push(hand.exec(expr).await?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,17 +111,17 @@ pub(crate) struct Fun {
|
|||||||
}
|
}
|
||||||
impl Fun {
|
impl Fun {
|
||||||
pub async fn new<I, O, F: ExprFunc<I, O>>(path: Sym, f: F) -> Self {
|
pub async fn new<I, O, F: ExprFunc<I, O>>(path: Sym, f: F) -> Self {
|
||||||
let ctx = ctx();
|
FUNS_CTX.with(|cx| {
|
||||||
let funs: &FunsCtx = ctx.get_or_default();
|
let mut fung = cx.borrow_mut();
|
||||||
let mut fung = funs.0.lock().await;
|
let record = if let Some(record) = fung.get(&(sys_id(), path.clone())) {
|
||||||
let record = if let Some(record) = fung.get(&path) {
|
|
||||||
record.clone()
|
record.clone()
|
||||||
} else {
|
} else {
|
||||||
let record = process_args(f);
|
let record = process_args(f);
|
||||||
fung.insert(path.clone(), record.clone());
|
fung.insert((sys_id(), path.clone()), record.clone());
|
||||||
record
|
record
|
||||||
};
|
};
|
||||||
Self { args: vec![], path, record }
|
Self { args: vec![], path, record }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
pub fn arity(&self) -> u8 { self.record.argtyps.len() as u8 }
|
pub fn arity(&self) -> u8 { self.record.argtyps.len() as u8 }
|
||||||
}
|
}
|
||||||
@@ -96,22 +132,21 @@ impl Atomic for Fun {
|
|||||||
impl OwnedAtom for Fun {
|
impl OwnedAtom for Fun {
|
||||||
type Refs = Vec<Expr>;
|
type Refs = Vec<Expr>;
|
||||||
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||||
async fn call_ref(&self, arg: Expr) -> GExpr {
|
async fn call_ref(&self, arg: Expr) -> impl ToExpr {
|
||||||
let new_args = self.args.iter().cloned().chain([arg]).collect_vec();
|
let new_args = self.args.iter().cloned().chain([arg]).collect_vec();
|
||||||
if new_args.len() == self.record.argtyps.len() {
|
if new_args.len() == self.record.argtyps.len() {
|
||||||
(self.record.fun)(new_args).await.to_gen().await
|
(self.record.fun)(new_args).await.to_gen().await
|
||||||
} else {
|
} else {
|
||||||
Self { args: new_args, record: self.record.clone(), path: self.path.clone() }.to_gen().await
|
new_atom(Self { args: new_args, record: self.record.clone(), path: self.path.clone() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await }
|
|
||||||
async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs {
|
async fn serialize(&self, write: Pin<&mut (impl AsyncWrite + ?Sized)>) -> Self::Refs {
|
||||||
self.path.to_api().encode(write).await;
|
self.path.to_api().encode(write).await.unwrap();
|
||||||
self.args.clone()
|
self.args.clone()
|
||||||
}
|
}
|
||||||
async fn deserialize(mut ds_cx: impl DeserializeCtx, args: Self::Refs) -> Self {
|
async fn deserialize(mut ds_cx: impl DeserializeCtx, args: Self::Refs) -> Self {
|
||||||
let path = Sym::from_api(ds_cx.decode().await, &i()).await;
|
let path = Sym::from_api(ds_cx.decode().await).await;
|
||||||
let record = (ctx().get::<FunsCtx>().0.lock().await.get(&path))
|
let record = (FUNS_CTX.with(|funs| funs.borrow().get(&(sys_id(), path.clone())).cloned()))
|
||||||
.expect("Function missing during deserialization")
|
.expect("Function missing during deserialization")
|
||||||
.clone();
|
.clone();
|
||||||
Self { args, path, record }
|
Self { args, path, record }
|
||||||
@@ -124,13 +159,14 @@ impl OwnedAtom for Fun {
|
|||||||
/// An Atom representing a partially applied native lambda. These are not
|
/// An Atom representing a partially applied native lambda. These are not
|
||||||
/// serializable.
|
/// serializable.
|
||||||
///
|
///
|
||||||
/// See [Fun] for the serializable variant
|
/// See [crate::fun] for the serializable variant
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Lambda {
|
pub struct Lambda {
|
||||||
args: Vec<Expr>,
|
args: Vec<Expr>,
|
||||||
record: FunRecord,
|
record: FunRecord,
|
||||||
}
|
}
|
||||||
impl Lambda {
|
impl Lambda {
|
||||||
|
/// Embed a lambda in an Orchid expression
|
||||||
pub fn new<I, O, F: ExprFunc<I, O>>(f: F) -> Self {
|
pub fn new<I, O, F: ExprFunc<I, O>>(f: F) -> Self {
|
||||||
Self { args: vec![], record: process_args(f) }
|
Self { args: vec![], record: process_args(f) }
|
||||||
}
|
}
|
||||||
@@ -142,24 +178,23 @@ impl Atomic for Lambda {
|
|||||||
impl OwnedAtom for Lambda {
|
impl OwnedAtom for Lambda {
|
||||||
type Refs = Never;
|
type Refs = Never;
|
||||||
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||||
async fn call_ref(&self, arg: Expr) -> GExpr {
|
async fn call_ref(&self, arg: Expr) -> impl ToExpr {
|
||||||
let new_args = self.args.iter().cloned().chain([arg]).collect_vec();
|
let new_args = self.args.iter().cloned().chain([arg]).collect_vec();
|
||||||
if new_args.len() == self.record.argtyps.len() {
|
if new_args.len() == self.record.argtyps.len() {
|
||||||
(self.record.fun)(new_args).await.to_gen().await
|
(self.record.fun)(new_args).await.to_gen().await
|
||||||
} else {
|
} else {
|
||||||
Self { args: new_args, record: self.record.clone() }.to_gen().await
|
new_atom(Self { args: new_args, record: self.record.clone() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn call(self, arg: Expr) -> GExpr { self.call_ref(arg).await }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod expr_func_derives {
|
mod expr_func_derives {
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use orchid_base::error::OrcRes;
|
use orchid_base::OrcRes;
|
||||||
|
|
||||||
use super::ExprFunc;
|
use super::{ARGV, ExprFunc};
|
||||||
use crate::conv::{ToExpr, TryFromExpr};
|
use crate::conv::{ToExpr, TryFromExpr};
|
||||||
use crate::func_atom::{ExecHandle, Expr};
|
use crate::func_atom::{ExecHandle, Expr};
|
||||||
use crate::gen_expr::GExpr;
|
use crate::gen_expr::GExpr;
|
||||||
@@ -178,8 +213,9 @@ mod expr_func_derives {
|
|||||||
}
|
}
|
||||||
async fn apply<'a>(&self, _: ExecHandle<'a>, v: Vec<Expr>) -> OrcRes<GExpr> {
|
async fn apply<'a>(&self, _: ExecHandle<'a>, v: Vec<Expr>) -> OrcRes<GExpr> {
|
||||||
assert_eq!(v.len(), Self::argtyps().len(), "Arity mismatch");
|
assert_eq!(v.len(), Self::argtyps().len(), "Arity mismatch");
|
||||||
|
let argv = v.clone();
|
||||||
let [$([< $t:lower >],)*] = v.try_into().unwrap_or_else(|_| panic!("Checked above"));
|
let [$([< $t:lower >],)*] = v.try_into().unwrap_or_else(|_| panic!("Checked above"));
|
||||||
Ok(self($($t::try_from_expr([< $t:lower >]).await?,)*).await.to_gen().await)
|
Ok(ARGV.scope(argv, self($($t::try_from_expr([< $t:lower >]).await?,)*)).await.to_gen().await)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,7 +226,7 @@ mod expr_func_derives {
|
|||||||
expr_func_derive!(A, B, C);
|
expr_func_derive!(A, B, C);
|
||||||
expr_func_derive!(A, B, C, D);
|
expr_func_derive!(A, B, C, D);
|
||||||
expr_func_derive!(A, B, C, D, E);
|
expr_func_derive!(A, B, C, D, E);
|
||||||
// expr_func_derive!(A, B, C, D, E, F);
|
expr_func_derive!(A, B, C, D, E, F);
|
||||||
// expr_func_derive!(A, B, C, D, E, F, G);
|
// expr_func_derive!(A, B, C, D, E, F, G);
|
||||||
// expr_func_derive!(A, B, C, D, E, F, G, H);
|
// expr_func_derive!(A, B, C, D, E, F, G, H);
|
||||||
// expr_func_derive!(A, B, C, D, E, F, G, H, I);
|
// expr_func_derive!(A, B, C, D, E, F, G, H, I);
|
||||||
|
|||||||
@@ -1,28 +1,45 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::pin::{Pin, pin};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::{FutureExt, Stream, StreamExt, stream};
|
||||||
use orchid_base::error::{OrcErr, OrcErrv};
|
use orchid_base::{
|
||||||
use orchid_base::format::{FmtCtx, FmtUnit, Format, Variants};
|
FmtCtx, FmtUnit, Format, OrcErr, OrcErrv, Pos, Sym, Variants, match_mapping, tl_cache,
|
||||||
use orchid_base::location::Pos;
|
};
|
||||||
use orchid_base::name::Sym;
|
use substack::Substack;
|
||||||
use orchid_base::reqnot::Requester;
|
use task_local::task_local;
|
||||||
use orchid_base::{match_mapping, tl_cache};
|
|
||||||
|
|
||||||
use crate::api;
|
use crate::{AtomFactory, AtomicFeatures, Expr, ToExpr, ToExprFuture, api, request, sys_id};
|
||||||
use crate::atom::{AtomFactory, ToAtom};
|
|
||||||
use crate::context::ctx;
|
|
||||||
use crate::expr::Expr;
|
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct ExprSerializeCx<'a> {
|
||||||
|
closures: Substack<'a, u64>,
|
||||||
|
lambda_counter: &'a RefCell<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Release notifications will not be sent for the slots. Use this with
|
||||||
|
/// messages that imply ownership transfer
|
||||||
|
pub async fn serialize(expr: GExpr) -> api::Expression {
|
||||||
|
let cx = ExprSerializeCx { closures: Substack::Bottom, lambda_counter: &RefCell::new(0) };
|
||||||
|
expr.serialize(cx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Smart object representing AST not-yet-sent to the interpreter. This type can
|
||||||
|
/// be cloned and persisted, and it must not have unbound arguments. The helper
|
||||||
|
/// functions in this module let you build trees of [ToExpr] implementors which
|
||||||
|
/// represent lambdas and their arguments separately, and then convert them into
|
||||||
|
/// [GExpr] in one pass.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct GExpr {
|
pub struct GExpr {
|
||||||
pub kind: GExprKind,
|
/// AST node type
|
||||||
pub pos: Pos,
|
kind: GExprKind,
|
||||||
|
/// Code location associated with the expression for debugging purposes
|
||||||
|
pos: Pos,
|
||||||
}
|
}
|
||||||
impl GExpr {
|
impl GExpr {
|
||||||
/// Release notifications will not be sent for the slots. Use this with
|
async fn serialize(self, cx: ExprSerializeCx<'_>) -> api::Expression {
|
||||||
/// messages that imply ownership transfer
|
|
||||||
pub async fn serialize(self) -> api::Expression {
|
|
||||||
if let GExprKind::Slot(ex) = self.kind {
|
if let GExprKind::Slot(ex) = self.kind {
|
||||||
let hand = ex.handle();
|
let hand = ex.handle();
|
||||||
mem::drop(ex);
|
mem::drop(ex);
|
||||||
@@ -33,14 +50,17 @@ impl GExpr {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
api::Expression {
|
api::Expression {
|
||||||
location: api::Location::Inherit,
|
location: self.pos.to_api(),
|
||||||
kind: self.kind.serialize().boxed_local().await,
|
kind: self.kind.serialize(cx).boxed_local().await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// Reassign location information. The typical default is [Pos::Inherit]
|
||||||
pub fn at(self, pos: Pos) -> Self { GExpr { pos, kind: self.kind } }
|
pub fn at(self, pos: Pos) -> Self { GExpr { pos, kind: self.kind } }
|
||||||
|
/// Send the expression to the interpreter to be compiled and to become
|
||||||
|
/// shareable across extensions
|
||||||
pub async fn create(self) -> Expr {
|
pub async fn create(self) -> Expr {
|
||||||
Expr::deserialize(ctx().reqnot().request(api::Create(self.serialize().await)).await).await
|
Expr::deserialize(request(api::Create(sys_id(), serialize(self).await)).await).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Format for GExpr {
|
impl Format for GExpr {
|
||||||
@@ -49,35 +69,69 @@ impl Format for GExpr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// AST nodes recognized by the interpreter
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum GExprKind {
|
pub enum GExprKind {
|
||||||
|
/// Function call
|
||||||
Call(Box<GExpr>, Box<GExpr>),
|
Call(Box<GExpr>, Box<GExpr>),
|
||||||
Lambda(u64, Box<GExpr>),
|
/// Lambda expression. Argument must be the same for slot
|
||||||
|
Lambda(Box<GExpr>),
|
||||||
|
/// Slot for a lambda argument
|
||||||
Arg(u64),
|
Arg(u64),
|
||||||
|
/// The second expression is only valid after the first one had already been
|
||||||
|
/// fully normalized. The main use case is the pattern `Lambda(0, Seq(0,
|
||||||
|
/// Call(foo, 0)))` where foo is an atom that attempts to downcast its
|
||||||
|
/// argument.
|
||||||
Seq(Box<GExpr>, Box<GExpr>),
|
Seq(Box<GExpr>, Box<GExpr>),
|
||||||
|
/// A reference to a constant from the shared constant tree. It is best to
|
||||||
|
/// mark the system that provides named constants as a dependency, but this is
|
||||||
|
/// not required
|
||||||
Const(Sym),
|
Const(Sym),
|
||||||
|
/// A newly created atom. Since at this point the atom needs to be registered
|
||||||
|
/// inside the extension but doesn't yet have an [api::ExprTicket], atoms need
|
||||||
|
/// their own [api::Atom::drop] if they have an identity
|
||||||
|
#[allow(private_interfaces)]
|
||||||
NewAtom(AtomFactory),
|
NewAtom(AtomFactory),
|
||||||
|
/// An expression previously registered or coming from outside the extension
|
||||||
Slot(Expr),
|
Slot(Expr),
|
||||||
|
/// A runtime error
|
||||||
Bottom(OrcErrv),
|
Bottom(OrcErrv),
|
||||||
}
|
}
|
||||||
impl GExprKind {
|
impl GExprKind {
|
||||||
pub async fn serialize(self) -> api::ExpressionKind {
|
pub fn at(self, pos: Pos) -> GExpr { GExpr { kind: self, pos } }
|
||||||
|
async fn serialize(self, cx: ExprSerializeCx<'_>) -> api::ExpressionKind {
|
||||||
match_mapping!(self, Self => api::ExpressionKind {
|
match_mapping!(self, Self => api::ExpressionKind {
|
||||||
Call(
|
Call(
|
||||||
f => Box::new(f.serialize().await),
|
f => Box::new(f.serialize(cx).await),
|
||||||
x => Box::new(x.serialize().await)
|
x => Box::new(x.serialize(cx).await)
|
||||||
),
|
),
|
||||||
Seq(
|
Seq(
|
||||||
a => Box::new(a.serialize().await),
|
a => Box::new(a.serialize(cx).await),
|
||||||
b => Box::new(b.serialize().await)
|
b => Box::new(b.serialize(cx).await)
|
||||||
),
|
),
|
||||||
Lambda(arg, body => Box::new(body.serialize().await)),
|
|
||||||
Arg(arg),
|
|
||||||
Const(name.to_api()),
|
Const(name.to_api()),
|
||||||
Bottom(err.to_api()),
|
Bottom(err.to_api()),
|
||||||
NewAtom(fac.clone().build().await),
|
NewAtom(fac.clone().build().await),
|
||||||
} {
|
} {
|
||||||
Self::Slot(_) => panic!("processed elsewhere")
|
Self::Slot(_) => panic!("processed elsewhere"),
|
||||||
|
Self::Lambda(body) => {
|
||||||
|
let id: u64;
|
||||||
|
{
|
||||||
|
let mut g = cx.lambda_counter.borrow_mut();
|
||||||
|
id = *g;
|
||||||
|
*g += 1;
|
||||||
|
};
|
||||||
|
let cx = ExprSerializeCx {
|
||||||
|
lambda_counter: cx.lambda_counter,
|
||||||
|
closures: cx.closures.push(id)
|
||||||
|
};
|
||||||
|
api::ExpressionKind::Lambda(id,
|
||||||
|
Box::new(body.serialize(cx).await)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Self::Arg(arg) => {
|
||||||
|
api::ExpressionKind::Arg(*cx.closures.iter().nth(arg as usize).expect("Unbound arg"))
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,9 +141,9 @@ impl Format for GExprKind {
|
|||||||
GExprKind::Call(f, x) =>
|
GExprKind::Call(f, x) =>
|
||||||
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("{0} ({1})")))
|
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("{0} ({1})")))
|
||||||
.units([f.print(c).await, x.print(c).await]),
|
.units([f.print(c).await, x.print(c).await]),
|
||||||
GExprKind::Lambda(arg, body) =>
|
GExprKind::Lambda(body) =>
|
||||||
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("\\{0}.{1}")))
|
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("\\{1}")))
|
||||||
.units([arg.to_string().into(), body.print(c).await]),
|
.units([body.print(c).await]),
|
||||||
GExprKind::Arg(arg) => arg.to_string().into(),
|
GExprKind::Arg(arg) => arg.to_string().into(),
|
||||||
GExprKind::Seq(a, b) =>
|
GExprKind::Seq(a, b) =>
|
||||||
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("[{0}] {1}")))
|
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("[{0}] {1}")))
|
||||||
@@ -104,30 +158,159 @@ impl Format for GExprKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inherit(kind: GExprKind) -> GExpr { GExpr { pos: Pos::Inherit, kind } }
|
pub fn inherit(kind: GExprKind) -> GExpr { GExpr { pos: Pos::Inherit, kind } }
|
||||||
|
|
||||||
pub fn sym_ref(path: Sym) -> GExpr { inherit(GExprKind::Const(path)) }
|
task_local! {
|
||||||
pub fn atom<A: ToAtom>(atom: A) -> GExpr { inherit(GExprKind::NewAtom(atom.to_atom_factory())) }
|
pub static CLOSURE_DEPTH: u64;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn seq(deps: impl IntoIterator<Item = GExpr>, val: GExpr) -> GExpr {
|
impl ToExpr for Sym {
|
||||||
fn recur(mut ops: impl Iterator<Item = GExpr>) -> Option<GExpr> {
|
async fn to_expr(self) -> Expr
|
||||||
let op = ops.next()?;
|
where Self: Sized {
|
||||||
Some(match recur(ops) {
|
self.to_gen().await.create().await
|
||||||
|
}
|
||||||
|
async fn to_gen(self) -> GExpr { inherit(GExprKind::Const(self)) }
|
||||||
|
}
|
||||||
|
/// Creates an expression from a new atom that we own.
|
||||||
|
pub fn new_atom<A: AtomicFeatures>(atom: A) -> GExpr { inherit(GExprKind::NewAtom(atom.factory())) }
|
||||||
|
|
||||||
|
pub fn slot(expr: Expr) -> GExpr { GExpr { pos: Pos::SlotTarget, kind: GExprKind::Slot(expr) } }
|
||||||
|
|
||||||
|
/// An expression which is only valid if a number of dependencies had already
|
||||||
|
/// been normalized
|
||||||
|
pub fn seq(
|
||||||
|
deps: impl IntoGExprStream,
|
||||||
|
val: impl ToExpr,
|
||||||
|
) -> ToExprFuture<impl Future<Output = GExpr>> {
|
||||||
|
ToExprFuture(async {
|
||||||
|
async fn recur(mut ops: Pin<&mut impl Stream<Item = GExpr>>) -> Option<GExpr> {
|
||||||
|
let op = ops.next().await?.to_gen().await;
|
||||||
|
Some(match recur(ops).boxed_local().await {
|
||||||
None => op,
|
None => op,
|
||||||
Some(rec) => inherit(GExprKind::Seq(Box::new(op), Box::new(rec))),
|
Some(rec) => inherit(GExprKind::Seq(Box::new(op), Box::new(rec))),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
recur(deps.into_iter().chain([val])).expect("Empty list provided to seq!")
|
recur(pin!(deps.into_gexpr_stream().chain(stream::iter([val.to_gen().await]))))
|
||||||
|
.await
|
||||||
|
.expect("Empty list provided to seq!")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arg(n: u64) -> GExpr { inherit(GExprKind::Arg(n)) }
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum ArgState {
|
||||||
pub fn lambda(n: u64, [b]: [GExpr; 1]) -> GExpr { inherit(GExprKind::Lambda(n, Box::new(b))) }
|
Building,
|
||||||
|
Serializing { depth: u64 },
|
||||||
pub fn call(f: GExpr, argv: impl IntoIterator<Item = GExpr>) -> GExpr {
|
Ready,
|
||||||
(argv.into_iter()).fold(f, |f, x| inherit(GExprKind::Call(Box::new(f), Box::new(x))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Argument bound by an enclosing [lam] or [dyn_lambda]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct GenArg<'a>(*const RefCell<ArgState>, PhantomData<&'a ()>);
|
||||||
|
impl ToExpr for GenArg<'_> {
|
||||||
|
async fn to_gen(self) -> GExpr {
|
||||||
|
// SAFETY: Created from a Rc that lives as long as the lifetime arg, see [lam]
|
||||||
|
let state = unsafe { self.0.as_ref().unwrap() };
|
||||||
|
match (*state.borrow(), CLOSURE_DEPTH.try_with(|r| *r)) {
|
||||||
|
(ArgState::Serializing { .. }, Err(_)) =>
|
||||||
|
panic!("Lambda should have cleared up argstate alongside CLOSURE_DEPTH"),
|
||||||
|
(ArgState::Serializing { depth }, Ok(total)) => inherit(GExprKind::Arg(total - depth)),
|
||||||
|
(ArgState::Building, _) =>
|
||||||
|
panic!("Argument serialized before lambda. Likely an over-eager ToExpr impl"),
|
||||||
|
(ArgState::Ready, _) =>
|
||||||
|
unreachable!("The arg should never be available this long, the GenArg is a convenience"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A lambda expression.
|
||||||
|
pub fn lam<'a>(
|
||||||
|
cb: impl for<'b> AsyncFnOnce(GenArg<'b>) -> GExpr + 'a,
|
||||||
|
) -> ToExprFuture<impl Future<Output = GExpr> + 'a> {
|
||||||
|
let state = Rc::new(RefCell::new(ArgState::Building));
|
||||||
|
ToExprFuture(async move {
|
||||||
|
let rank = CLOSURE_DEPTH.try_with(|r| *r + 1).unwrap_or(0);
|
||||||
|
match *state.borrow_mut() {
|
||||||
|
ref mut state @ ArgState::Building => *state = ArgState::Serializing { depth: rank },
|
||||||
|
ArgState::Serializing { .. } => panic!("Lambda serialized twice, found interrupted"),
|
||||||
|
ArgState::Ready => panic!("Lambda serialized twice"),
|
||||||
|
}
|
||||||
|
let gen_arg = GenArg(Rc::as_ptr(&state), PhantomData);
|
||||||
|
let ret = CLOSURE_DEPTH.scope(rank, async { cb(gen_arg).await.to_gen().await }).await;
|
||||||
|
mem::drop(state);
|
||||||
|
inherit(GExprKind::Lambda(Box::new(ret)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// one or more items that are convertible to expressions. In practice, a
|
||||||
|
/// [ToExpr], [Vec<GExpr>], or a tuple of types that all implement [ToExpr]. For
|
||||||
|
/// compilation performance, the tuple's arity may not be more than 6
|
||||||
|
pub trait IntoGExprStream {
|
||||||
|
/// Convert each item to an expression and return them
|
||||||
|
fn into_gexpr_stream(self) -> impl Stream<Item = GExpr>;
|
||||||
|
}
|
||||||
|
impl<T: ToExpr> IntoGExprStream for T {
|
||||||
|
fn into_gexpr_stream(self) -> impl Stream<Item = GExpr> { (self,).into_gexpr_stream() }
|
||||||
|
}
|
||||||
|
impl IntoGExprStream for Vec<GExpr> {
|
||||||
|
fn into_gexpr_stream(self) -> impl Stream<Item = GExpr> { stream::iter(self) }
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tuple_impls {
|
||||||
|
use futures::{Stream, StreamExt, stream};
|
||||||
|
|
||||||
|
use super::IntoGExprStream;
|
||||||
|
use crate::conv::ToExpr;
|
||||||
|
use crate::gen_expr::GExpr;
|
||||||
|
|
||||||
|
macro_rules! tuple_impl {
|
||||||
|
($($T:ident)*) => {
|
||||||
|
pastey::paste!{
|
||||||
|
impl<$($T: ToExpr),*> IntoGExprStream for ($($T,)*) {
|
||||||
|
fn into_gexpr_stream(self) -> impl Stream<Item = GExpr> {
|
||||||
|
let ($([< $T:snake >],)*) = self;
|
||||||
|
stream::once(async { stream::iter([$([< $T:snake >].to_gen().await,)*]) }).flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tuple_impl!();
|
||||||
|
tuple_impl!(A);
|
||||||
|
tuple_impl!(A B);
|
||||||
|
tuple_impl!(A B C);
|
||||||
|
tuple_impl!(A B C D);
|
||||||
|
tuple_impl!(A B C D E);
|
||||||
|
tuple_impl!(A B C D E F);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a (curried) function
|
||||||
|
pub fn call(
|
||||||
|
f: impl ToExpr,
|
||||||
|
argv: impl IntoGExprStream,
|
||||||
|
) -> ToExprFuture<impl Future<Output = GExpr>> {
|
||||||
|
ToExprFuture(async {
|
||||||
|
(argv.into_gexpr_stream())
|
||||||
|
.fold(f.to_gen().await, async |f, x| inherit(GExprKind::Call(Box::new(f), Box::new(x))))
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a function on a dynamic number of arguments
|
||||||
|
pub fn call_v(
|
||||||
|
f: impl ToExpr,
|
||||||
|
argv: impl IntoIterator<Item: ToExpr>,
|
||||||
|
) -> ToExprFuture<impl Future<Output = GExpr>> {
|
||||||
|
ToExprFuture(async {
|
||||||
|
stream::iter(argv)
|
||||||
|
.fold(f.to_gen().await, async |f, x| {
|
||||||
|
inherit(GExprKind::Call(Box::new(f), Box::new(x.to_gen().await)))
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A runtime error
|
||||||
pub fn bot(ev: impl IntoIterator<Item = OrcErr>) -> GExpr {
|
pub fn bot(ev: impl IntoIterator<Item = OrcErr>) -> GExpr {
|
||||||
inherit(GExprKind::Bottom(OrcErrv::new(ev).unwrap()))
|
inherit(GExprKind::Bottom(OrcErrv::new(ev).unwrap()))
|
||||||
}
|
}
|
||||||
|
|||||||
48
orchid-extension/src/interner.rs
Normal file
48
orchid-extension/src/interner.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use futures::future::{LocalBoxFuture, join_all, ready};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use orchid_base::local_interner::{Int, StrBranch, StrvBranch};
|
||||||
|
use orchid_base::{IStr, IStrv, InternerSrv};
|
||||||
|
|
||||||
|
use crate::{api, mute_reply, request};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ExtInterner {
|
||||||
|
str: Int<StrBranch>,
|
||||||
|
strv: Int<StrvBranch>,
|
||||||
|
}
|
||||||
|
impl InternerSrv for ExtInterner {
|
||||||
|
fn is<'a>(&'a self, v: &'a str) -> LocalBoxFuture<'a, IStr> {
|
||||||
|
match self.str.i(v) {
|
||||||
|
Ok(i) => Box::pin(ready(i)),
|
||||||
|
Err(e) =>
|
||||||
|
Box::pin(async { e.set_if_empty(mute_reply(request(api::InternStr(v.to_owned()))).await) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, IStr> {
|
||||||
|
match self.str.e(t) {
|
||||||
|
Ok(i) => Box::pin(ready(i)),
|
||||||
|
Err(e) => Box::pin(async move { e.set_if_empty(Rc::new(request(api::ExternStr(t)).await)) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn iv<'a>(&'a self, v: &'a [IStr]) -> LocalBoxFuture<'a, IStrv> {
|
||||||
|
match self.strv.i(v) {
|
||||||
|
Ok(i) => Box::pin(ready(i)),
|
||||||
|
Err(e) => Box::pin(async {
|
||||||
|
e.set_if_empty(request(api::InternStrv(v.iter().map(|is| is.to_api()).collect_vec())).await)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn ev(&self, t: orchid_api::TStrv) -> LocalBoxFuture<'_, IStrv> {
|
||||||
|
match self.strv.e(t) {
|
||||||
|
Ok(i) => Box::pin(ready(i)),
|
||||||
|
Err(e) => Box::pin(async move {
|
||||||
|
let tstr_v = request(api::ExternStrv(t)).await;
|
||||||
|
e.set_if_empty(Rc::new(join_all(tstr_v.into_iter().map(|t| self.es(t))).await))
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_interner() -> Rc<dyn InternerSrv> { Rc::<ExtInterner>::default() }
|
||||||
@@ -1,102 +1,125 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use futures::future::LocalBoxFuture;
|
use futures::future::LocalBoxFuture;
|
||||||
use orchid_base::error::{OrcErrv, OrcRes, Reporter, mk_errv};
|
use orchid_base::{IStr, OrcErrv, OrcRes, Pos, SrcRange, Sym, is, mk_errv};
|
||||||
use orchid_base::interner::{Interner, Tok};
|
|
||||||
use orchid_base::location::{Pos, SrcRange};
|
|
||||||
use orchid_base::name::Sym;
|
|
||||||
use orchid_base::parse::ParseCtx;
|
|
||||||
use orchid_base::reqnot::Requester;
|
|
||||||
|
|
||||||
use crate::api;
|
|
||||||
use crate::context::{ctx, i};
|
|
||||||
use crate::expr::BorrowedExprStore;
|
|
||||||
use crate::parser::PTokTree;
|
|
||||||
use crate::tree::GenTokTree;
|
use crate::tree::GenTokTree;
|
||||||
|
use crate::{BorrowedExprStore, PTokTree, api, request};
|
||||||
|
|
||||||
pub async fn ekey_cascade() -> Tok<String> {
|
pub(crate) async fn ekey_cascade() -> IStr { is("An error cascading from a recursive call").await }
|
||||||
i().i("An error cascading from a recursive call").await
|
pub(crate) async fn ekey_not_applicable() -> IStr {
|
||||||
}
|
is("Pseudo-error to communicate that the current branch in a dispatch doesn't apply").await
|
||||||
pub async fn ekey_not_applicable() -> Tok<String> {
|
|
||||||
i().i("Pseudo-error to communicate that the current branch in a dispatch doesn't apply").await
|
|
||||||
}
|
}
|
||||||
const MSG_INTERNAL_ERROR: &str = "This error is a sentinel for the extension library.\
|
const MSG_INTERNAL_ERROR: &str = "This error is a sentinel for the extension library.\
|
||||||
it should not be emitted by the extension.";
|
it should not be emitted by the extension.";
|
||||||
|
|
||||||
pub async fn err_cascade() -> OrcErrv {
|
pub(crate) async fn err_cascade() -> OrcErrv {
|
||||||
mk_errv(ekey_cascade().await, MSG_INTERNAL_ERROR, [Pos::None])
|
mk_errv(ekey_cascade().await, MSG_INTERNAL_ERROR, [Pos::None])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return this error if your lexer can determine that it is not applicable to
|
||||||
|
/// this piece of syntax. This error will not be raised if another lexer
|
||||||
|
/// matches, or if the piece of matched syntax is found to be valid until
|
||||||
|
/// runtime
|
||||||
pub async fn err_not_applicable() -> OrcErrv {
|
pub async fn err_not_applicable() -> OrcErrv {
|
||||||
mk_errv(ekey_not_applicable().await, MSG_INTERNAL_ERROR, [Pos::None])
|
mk_errv(ekey_not_applicable().await, MSG_INTERNAL_ERROR, [Pos::None])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Object passed to lexers for recursion and position-related convenience
|
||||||
|
/// methods
|
||||||
pub struct LexContext<'a> {
|
pub struct LexContext<'a> {
|
||||||
pub(crate) exprs: &'a BorrowedExprStore,
|
pub(crate) exprs: &'a BorrowedExprStore,
|
||||||
pub text: &'a Tok<String>,
|
pub text: &'a IStr,
|
||||||
pub id: api::ParsId,
|
pub id: api::ParsId,
|
||||||
pub pos: u32,
|
pub pos: u32,
|
||||||
i: Interner,
|
|
||||||
pub(crate) src: Sym,
|
pub(crate) src: Sym,
|
||||||
pub(crate) rep: &'a Reporter,
|
|
||||||
}
|
}
|
||||||
impl<'a> LexContext<'a> {
|
impl<'a> LexContext<'a> {
|
||||||
pub fn new(
|
pub(crate) fn new(
|
||||||
exprs: &'a BorrowedExprStore,
|
exprs: &'a BorrowedExprStore,
|
||||||
text: &'a Tok<String>,
|
text: &'a IStr,
|
||||||
id: api::ParsId,
|
id: api::ParsId,
|
||||||
pos: u32,
|
pos: u32,
|
||||||
src: Sym,
|
src: Sym,
|
||||||
rep: &'a Reporter,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { exprs, i: i(), id, pos, rep, src, text }
|
Self { exprs, id, pos, src, text }
|
||||||
}
|
}
|
||||||
|
/// The logical path of the source file, also the path of the file's root
|
||||||
|
/// module
|
||||||
pub fn src(&self) -> &Sym { &self.src }
|
pub fn src(&self) -> &Sym { &self.src }
|
||||||
|
/// Lex an interpolated expression of some kind
|
||||||
|
///
|
||||||
/// This function returns [PTokTree] because it can never return
|
/// This function returns [PTokTree] because it can never return
|
||||||
/// [orchid_base::tree::Token::NewExpr]. You can use
|
/// [orchid_base::Token::NewExpr]. You can use
|
||||||
/// [crate::parser::p_tree2gen] to convert this to [crate::tree::GenTokTree]
|
/// [crate::parser::p_tree2gen] to convert this to [crate::tree::GenTokTree]
|
||||||
/// for embedding in the return value.
|
/// for embedding in the return value.
|
||||||
pub async fn recurse(&self, tail: &'a str) -> OrcRes<(&'a str, PTokTree)> {
|
pub async fn recurse(&self, tail: &'a str) -> OrcRes<(&'a str, PTokTree)> {
|
||||||
let start = self.pos(tail);
|
let start = self.pos(tail);
|
||||||
let Some(lx) = ctx().reqnot().request(api::SubLex { pos: start, id: self.id }).await else {
|
let Some(lx) = request(api::SubLex { pos: start, id: self.id }).await else {
|
||||||
return Err(err_cascade().await);
|
return Err(err_cascade().await);
|
||||||
};
|
};
|
||||||
let tree = PTokTree::from_api(&lx.tree, &mut { self.exprs }, &mut (), &self.src, &i()).await;
|
let tree = PTokTree::from_api(lx.tree, &mut { self.exprs }, &mut (), &self.src).await;
|
||||||
Ok((&self.text[lx.pos as usize..], tree))
|
Ok((&self.text[lx.pos as usize..], tree))
|
||||||
}
|
}
|
||||||
|
/// Find the index of a cursor given the remaining, not-yet-consumed text
|
||||||
pub fn pos(&self, tail: &'a str) -> u32 { (self.text.len() - tail.len()) as u32 }
|
pub fn pos(&self, tail: &'a str) -> u32 { (self.text.len() - tail.len()) as u32 }
|
||||||
|
/// Convenience method to find the source position of a token given the text
|
||||||
|
/// it was found in and the text after it was parsed.
|
||||||
pub fn pos_tt(&self, tail_with: &'a str, tail_without: &'a str) -> SrcRange {
|
pub fn pos_tt(&self, tail_with: &'a str, tail_without: &'a str) -> SrcRange {
|
||||||
SrcRange::new(self.pos(tail_with)..self.pos(tail_without), &self.src)
|
SrcRange::new(self.pos(tail_with)..self.pos(tail_without), &self.src)
|
||||||
}
|
}
|
||||||
|
/// Convenience method to find the source position of a token given its length
|
||||||
|
/// and the remaining text afterwards. The length can be any number type but
|
||||||
|
/// must convert to a u32 without errors
|
||||||
pub fn pos_lt(&self, len: impl TryInto<u32, Error: fmt::Debug>, tail: &'a str) -> SrcRange {
|
pub fn pos_lt(&self, len: impl TryInto<u32, Error: fmt::Debug>, tail: &'a str) -> SrcRange {
|
||||||
SrcRange::new(self.pos(tail) - len.try_into().unwrap()..self.pos(tail), &self.src)
|
SrcRange::new(self.pos(tail) - len.try_into().unwrap()..self.pos(tail), &self.src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl ParseCtx for LexContext<'_> {
|
|
||||||
fn i(&self) -> &Interner { &self.i }
|
/// One or more tokens returned by the parser. In practice, [GenTokTree],
|
||||||
fn rep(&self) -> &Reporter { self.rep }
|
/// `Vec<GenTokTree>`, or `[GenTokTree; usize]`
|
||||||
|
pub trait LexedData {
|
||||||
|
fn into_vec(self) -> Vec<GenTokTree>;
|
||||||
|
}
|
||||||
|
impl LexedData for GenTokTree {
|
||||||
|
fn into_vec(self) -> Vec<GenTokTree> { vec![self] }
|
||||||
|
}
|
||||||
|
impl LexedData for Vec<GenTokTree> {
|
||||||
|
fn into_vec(self) -> Vec<GenTokTree> { self }
|
||||||
|
}
|
||||||
|
impl<const N: usize> LexedData for [GenTokTree; N] {
|
||||||
|
fn into_vec(self) -> Vec<GenTokTree> { self.to_vec() }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Lexer: Send + Sync + Sized + Default + 'static {
|
/// A lexer plugin to extend the syntax of Orchid
|
||||||
|
pub trait Lexer: Debug + Send + Sync + Sized + Default + 'static {
|
||||||
|
/// As an optimization, your lexer will only receive snippets that start with
|
||||||
|
/// a character included in one of these ranges. If you have a multi-character
|
||||||
|
/// discriminator, include all possible starting chars in this and return
|
||||||
|
/// [err_not_applicable] if the entire discriminator was not found
|
||||||
const CHAR_FILTER: &'static [RangeInclusive<char>];
|
const CHAR_FILTER: &'static [RangeInclusive<char>];
|
||||||
|
/// Attempt to lex some custom syntax from the start of the tail string.
|
||||||
|
/// Return the remaining text and the lexed tokens.
|
||||||
fn lex<'a>(
|
fn lex<'a>(
|
||||||
tail: &'a str,
|
tail: &'a str,
|
||||||
lctx: &'a LexContext<'a>,
|
lctx: &'a LexContext<'a>,
|
||||||
) -> impl Future<Output = OrcRes<(&'a str, GenTokTree)>>;
|
) -> impl Future<Output = OrcRes<(&'a str, impl LexedData)>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DynLexer: Send + Sync + 'static {
|
/// Type-erased [Lexer]
|
||||||
|
pub trait DynLexer: Debug + Send + Sync + 'static {
|
||||||
|
/// Type-erased [Lexer::CHAR_FILTER]
|
||||||
fn char_filter(&self) -> &'static [RangeInclusive<char>];
|
fn char_filter(&self) -> &'static [RangeInclusive<char>];
|
||||||
|
/// Type-erased [Lexer::lex]
|
||||||
fn lex<'a>(
|
fn lex<'a>(
|
||||||
&self,
|
&self,
|
||||||
tail: &'a str,
|
tail: &'a str,
|
||||||
ctx: &'a LexContext<'a>,
|
ctx: &'a LexContext<'a>,
|
||||||
) -> LocalBoxFuture<'a, OrcRes<(&'a str, GenTokTree)>>;
|
) -> LocalBoxFuture<'a, OrcRes<(&'a str, Vec<GenTokTree>)>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Lexer> DynLexer for T {
|
impl<T: Lexer> DynLexer for T {
|
||||||
@@ -105,9 +128,11 @@ impl<T: Lexer> DynLexer for T {
|
|||||||
&self,
|
&self,
|
||||||
tail: &'a str,
|
tail: &'a str,
|
||||||
ctx: &'a LexContext<'a>,
|
ctx: &'a LexContext<'a>,
|
||||||
) -> LocalBoxFuture<'a, OrcRes<(&'a str, GenTokTree)>> {
|
) -> LocalBoxFuture<'a, OrcRes<(&'a str, Vec<GenTokTree>)>> {
|
||||||
T::lex(tail, ctx).boxed_local()
|
async { T::lex(tail, ctx).await.map(|(s, d)| (s, d.into_vec())) }.boxed_local()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Type-erased instance of a lexer that is returned by
|
||||||
|
/// [crate::System::lexers]
|
||||||
pub type LexerObj = &'static dyn DynLexer;
|
pub type LexerObj = &'static dyn DynLexer;
|
||||||
|
|||||||
@@ -1,21 +1,46 @@
|
|||||||
|
#![allow(refining_impl_trait, reason = "Has various false-positives around lints")]
|
||||||
use orchid_api as api;
|
use orchid_api as api;
|
||||||
|
|
||||||
pub mod atom;
|
mod atom;
|
||||||
pub mod atom_owned;
|
pub use atom::*;
|
||||||
pub mod atom_thin;
|
mod cmd_atom;
|
||||||
pub mod conv;
|
pub use cmd_atom::*;
|
||||||
pub mod coroutine_exec;
|
mod atom_owned;
|
||||||
pub mod entrypoint;
|
pub use atom_owned::*;
|
||||||
pub mod expr;
|
mod atom_thin;
|
||||||
pub mod func_atom;
|
pub use atom_thin::*;
|
||||||
|
pub mod binary;
|
||||||
|
mod conv;
|
||||||
|
pub use conv::*;
|
||||||
|
mod coroutine_exec;
|
||||||
|
pub use coroutine_exec::*;
|
||||||
|
mod entrypoint;
|
||||||
|
pub use entrypoint::*;
|
||||||
|
mod expr;
|
||||||
|
pub use expr::*;
|
||||||
|
mod ext_port;
|
||||||
|
pub use ext_port::*;
|
||||||
|
mod func_atom;
|
||||||
|
pub use func_atom::*;
|
||||||
pub mod gen_expr;
|
pub mod gen_expr;
|
||||||
pub mod lexer;
|
mod interner;
|
||||||
// pub mod msg;
|
mod lexer;
|
||||||
pub mod context;
|
pub use lexer::*;
|
||||||
pub mod other_system;
|
mod logger;
|
||||||
pub mod parser;
|
mod other_system;
|
||||||
pub mod reflection;
|
pub use other_system::*;
|
||||||
pub mod system;
|
mod parser;
|
||||||
pub mod system_ctor;
|
pub use parser::*;
|
||||||
pub mod tokio;
|
mod reflection;
|
||||||
|
pub use reflection::*;
|
||||||
|
pub mod std_reqs;
|
||||||
|
mod system;
|
||||||
|
pub use system::*;
|
||||||
|
mod system_ctor;
|
||||||
|
pub use system_ctor::*;
|
||||||
|
mod system_card;
|
||||||
|
pub use system_card::*;
|
||||||
|
mod tokio;
|
||||||
|
pub use tokio::*;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
|
mod trivial_req;
|
||||||
|
|||||||
55
orchid-extension/src/logger.rs
Normal file
55
orchid-extension/src/logger.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use std::fmt::Arguments;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use futures::future::LocalBoxFuture;
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use orchid_base::{LogWriter, Logger, is};
|
||||||
|
|
||||||
|
use crate::{api, notify};
|
||||||
|
|
||||||
|
pub(crate) struct LogWriterImpl {
|
||||||
|
category: String,
|
||||||
|
strat: api::LogStrategy,
|
||||||
|
}
|
||||||
|
impl LogWriter for LogWriterImpl {
|
||||||
|
fn write_fmt<'a>(&'a self, fmt: Arguments<'a>) -> LocalBoxFuture<'a, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
match &self.strat {
|
||||||
|
api::LogStrategy::Discard => (),
|
||||||
|
api::LogStrategy::Default =>
|
||||||
|
notify(api::Log { category: is(&self.category).await.to_api(), message: fmt.to_string() })
|
||||||
|
.await,
|
||||||
|
api::LogStrategy::File { path, .. } => {
|
||||||
|
let mut file = (File::options().write(true).create(true).truncate(false).open(path))
|
||||||
|
.unwrap_or_else(|e| panic!("Could not open {path}: {e}"));
|
||||||
|
file.write_fmt(fmt).unwrap_or_else(|e| panic!("Could not write to {path}: {e}"));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct LoggerImpl {
|
||||||
|
default: Option<api::LogStrategy>,
|
||||||
|
routing: HashMap<String, api::LogStrategy>,
|
||||||
|
}
|
||||||
|
impl LoggerImpl {
|
||||||
|
pub fn from_api(api: &api::Logger) -> Self {
|
||||||
|
Self {
|
||||||
|
default: api.default.clone(),
|
||||||
|
routing: api.routing.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Logger for LoggerImpl {
|
||||||
|
fn writer(&self, category: &str) -> Rc<dyn LogWriter> {
|
||||||
|
Rc::new(LogWriterImpl { category: category.to_string(), strat: self.strat(category) })
|
||||||
|
}
|
||||||
|
fn strat(&self, category: &str) -> orchid_api::LogStrategy {
|
||||||
|
(self.routing.get(category).cloned().or(self.default.clone()))
|
||||||
|
.expect("Unrecognized log category with no default strategy")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
use std::pin::pin;
|
|
||||||
|
|
||||||
use async_once_cell::OnceCell;
|
|
||||||
use futures::lock::Mutex;
|
|
||||||
use orchid_base::msg::{recv_msg, send_msg};
|
|
||||||
|
|
||||||
pub async fn send_parent_msg(msg: &[u8]) -> io::Result<()> {
|
|
||||||
let stdout_lk = STDOUT.get_or_init(async { Mutex::new(io::stdout()) }).await;
|
|
||||||
let mut stdout_g = stdout_lk.lock().await;
|
|
||||||
send_msg(pin!(&mut *stdout_g), msg).await
|
|
||||||
}
|
|
||||||
pub async fn recv_parent_msg() -> io::Result<Vec<u8>> { recv_msg(pin!(io::stdin())).await }
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user