consolidated scheduler API in pursuit of a deadlock

This commit is contained in:
lbfalvy committed 2026-06-23 12:33:16 +00:00
1 parent f1493ad35d
commit 490ca6d8b1
102 files changed
+1058 -706

No files matched your search

+1
View File
@@ -2,6 +2,7 @@
xtask = "run --quiet --package xtask --"
orcx = "xtask orcx --"
orcxdb = "xtask orcxdb --"
build-orcx = "xtask build-orcx --"
[env]
CARGO_WORKSPACE_DIR = { value = "", relative = true }
+36 -3
View File
@@ -8,9 +8,42 @@
"type": "lldb",
"request": "launch",
"name": "Debug",
"program": "${workspaceFolder}/<executable file>",
"args": [],
"cwd": "${workspaceFolder}"
"cargo": {
"cwd": "${workspaceFolder}",
"problemMatcher": "$rustc",
"args": [
"build-orcx",
],
"filter": {
"kind": "bin",
"name": "orcx"
}
},
"cwd": "${workspaceFolder}",
"env": {
"ORCHID_EXTENSIONS": "target/debug/orchid_std",
"ORCHID_DEFAULT_SYSTEMS": "orchid::std;orchid::macros",
"ORCHID_LOG_BUFFERS": "true"
},
"args": [
"--proj=examples/hello-world",
"--log=stderr",
"--log=msg:stderr",
"exec",
"--main='src::main::main'"
]
}
],
"inputs": [
{
"id": "pickOrcxProcess",
"type": "command",
"command": "lldb.pickProcess",
"args": {
// orcx appears literally in the cargo and xtask commands,
// so we use a / to search for a path which should only match the executable itself
"filter": "/orcx"
}
}
]
}
View File
Whitespace-only changes.
View File
Whitespace-only changes.
Generated
+74 -72
View File
@@ -138,8 +138,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -209,8 +209,8 @@ dependencies = [
"once_cell",
"proc-macro-crate",
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -243,7 +243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"quote 1.0.46",
"syn 1.0.109",
]
@@ -261,9 +261,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "camino"
version = "1.2.2"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
checksum = "b4ce8d3bd5823c7504d3f579f13e7b2f3da252fcb938c594d5680ee508bf846f"
[[package]]
name = "cast"
@@ -306,9 +306,9 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.44"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"js-sys",
@@ -347,8 +347,8 @@ checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [
"heck",
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -570,8 +570,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -677,9 +677,9 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "hashbrown"
version = "0.17.0"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"allocator-api2",
"equivalent",
@@ -738,7 +738,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"quote 1.0.46",
]
[[package]]
@@ -761,9 +761,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.14.0"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
dependencies = [
"either",
]
@@ -1037,8 +1037,8 @@ dependencies = [
"itertools",
"orchid-api-traits",
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -1065,13 +1065,14 @@ dependencies = [
name = "orchid-base"
version = "0.1.0"
dependencies = [
"async-event",
"async-fn-stream",
"async-once-cell",
"bound",
"derive_destructure",
"dyn-clone",
"futures",
"hashbrown 0.17.0",
"hashbrown 0.17.1",
"itertools",
"lazy_static",
"never",
@@ -1087,6 +1088,7 @@ dependencies = [
"rust-embed",
"substack",
"task-local",
"tokio",
"trait-set",
"unsync-pipe",
]
@@ -1103,7 +1105,7 @@ dependencies = [
"dyn-clone",
"futures",
"futures-locks",
"hashbrown 0.17.0",
"hashbrown 0.17.1",
"include_dir",
"itertools",
"konst",
@@ -1138,7 +1140,7 @@ dependencies = [
"derive_destructure",
"futures",
"futures-locks",
"hashbrown 0.17.0",
"hashbrown 0.17.1",
"itertools",
"lazy_static",
"libloading",
@@ -1169,7 +1171,7 @@ dependencies = [
"async-once-cell",
"chrono",
"futures",
"hashbrown 0.17.0",
"hashbrown 0.17.1",
"itertools",
"never",
"once_cell",
@@ -1243,9 +1245,9 @@ dependencies = [
[[package]]
name = "pastey"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5a797f0e07bdf071d15742978fc3128ec6c22891c31a3a931513263904c982a"
checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
[[package]]
name = "pin-project-lite"
@@ -1269,7 +1271,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2 1.0.106",
"syn 2.0.117",
"syn 2.0.118",
]
[[package]]
@@ -1331,7 +1333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"quote 1.0.46",
"syn 1.0.109",
]
@@ -1346,9 +1348,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.45"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2 1.0.106",
]
@@ -1433,9 +1435,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.3"
version = "1.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
dependencies = [
"aho-corasick",
"memchr",
@@ -1456,9 +1458,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.5"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "rend"
@@ -1494,7 +1496,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"quote 1.0.46",
"syn 1.0.109",
]
@@ -1516,9 +1518,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"quote 1.0.46",
"rust-embed-utils",
"syn 2.0.117",
"syn 2.0.118",
"walkdir",
]
@@ -1534,9 +1536,9 @@ dependencies = [
[[package]]
name = "rust_decimal"
version = "1.41.0"
version = "1.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a"
checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a"
dependencies = [
"arrayvec",
"borsh",
@@ -1620,8 +1622,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -1772,18 +1774,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"quote 1.0.46",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.117"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"quote 1.0.46",
"unicode-ident",
]
@@ -1823,8 +1825,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b68fbf53af4a8dfdf0bd9192ab2f6fd42e5b59454bcde58fe1382a8677087732"
dependencies = [
"proc-macro-crate",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
"wasm-bindgen-test",
]
@@ -1844,8 +1846,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -1874,9 +1876,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.52.1"
version = "1.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [
"bytes",
"libc",
@@ -1896,8 +1898,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -1962,8 +1964,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -2012,7 +2014,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"quote 1.0.46",
"syn 1.0.109",
]
@@ -2036,9 +2038,9 @@ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "unicode-segmentation"
version = "1.13.2"
version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "unicode-xid"
@@ -2156,7 +2158,7 @@ version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
dependencies = [
"quote 1.0.45",
"quote 1.0.46",
"wasm-bindgen-macro-support",
]
@@ -2168,8 +2170,8 @@ checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
dependencies = [
"bumpalo",
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
"wasm-bindgen-shared",
]
@@ -2210,8 +2212,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7150335716dce6028bead2b848e72f47b45e7b9422f64cccdc23bedca89affc1"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -2330,8 +2332,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -2341,8 +2343,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
[[package]]
@@ -2553,7 +2555,7 @@ dependencies = [
"heck",
"indexmap",
"prettyplease",
"syn 2.0.117",
"syn 2.0.118",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
@@ -2568,8 +2570,8 @@ dependencies = [
"anyhow",
"prettyplease",
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
"wit-bindgen-core",
"wit-bindgen-rust",
]
@@ -2644,6 +2646,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2 1.0.106",
"quote 1.0.45",
"syn 2.0.117",
"quote 1.0.46",
"syn 2.0.118",
]
+1 -1
View File
@@ -1,4 +1,4 @@
let main = macros::resolve '(
let main = (
stdio::get_stdout (ref stdout) =>
std::stream::write_str stdout "Hello, World!\n" (result::ok _) =>
std::stream::flush stdout (result::ok _) =>
+3 -3
View File
@@ -9,8 +9,8 @@ proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
quote = "1.0.45"
syn = { version = "2.0.117" }
quote = "1.0.46"
syn = { version = "2.0.118" }
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
proc-macro2 = "1.0.106"
itertools = "0.14.0"
itertools = "0.15.0"
+2 -2
View File
@@ -6,8 +6,8 @@ edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = "0.4.44"
chrono = "0.4.45"
futures = { version = "0.3.32", features = ["std"], default-features = false }
itertools = "0.14.0"
itertools = "0.15.0"
never = "0.1.0"
ordered-float = "5.3.0"
+1 -1
View File
@@ -10,7 +10,7 @@ ordered-float = "5.3.0"
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
futures = { version = "0.3.32", features = ["std"], default-features = false }
itertools = "0.14.0"
itertools = "0.15.0"
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
[dev-dependencies]
+21 -6
View File
@@ -41,7 +41,7 @@ pub struct FutureContextBin {
/// ABI-stable `Poll<()>`
#[derive(Clone, Copy)]
#[repr(C)]
pub enum UnitPoll {
pub enum UnitPollBin {
Pending,
Ready,
}
@@ -57,7 +57,20 @@ pub struct FutureBin {
/// `self`
pub drop: extern "C" fn(*const ()),
/// `&mut self` Equivalent to [Future::poll]
pub poll: extern "C" fn(*const (), FutureContextBin) -> UnitPoll,
pub poll: extern "C" fn(*const (), FutureContextBin) -> UnitPollBin,
}
#[derive(Clone, Copy)]
#[repr(C)]
pub struct TaskHandleBin {
pub data: *const (),
/// `self` The task continues
pub drop: extern "C" fn(*const ()),
/// `self` The future is never polled again.
pub abort: extern "C" fn(*const ()),
/// `self` Returns a future that completes when the task completes or when
/// it's aborted
pub join: extern "C" fn(*const ()) -> FutureBin,
}
/// Handle for a runtime that allows its holder to spawn futures across dynamic
@@ -68,10 +81,12 @@ 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),
/// `&self` Add a future to this extension's task. If the bool is true, this
/// future will keep the extension alive, and only be dropped on a forced
/// shutdown.
pub spawn: extern "C" fn(*const (), bool, FutureBin) -> TaskHandleBin,
/// `&self` wait until the specified time has passed before waking this future
pub sleep: extern "C" fn(*const (), u64) -> FutureBin,
}
/// Extension context.
+25 -2
View File
@@ -1,3 +1,6 @@
use core::fmt;
use std::fmt::Debug;
use std::hash::Hash;
use std::num::NonZeroU64;
use orchid_api_derive::{Coding, Hierarchy};
@@ -36,7 +39,7 @@ impl Request for InternStr {
#[extends(IntReq, ExtHostReq)]
pub struct ExternStr(pub TStr);
impl Request for ExternStr {
type Response = String;
type Response = Result<String, StaleTokenError>;
}
/// replica -> master to intern a vector of interned strings
///
@@ -59,7 +62,7 @@ impl Request for InternStrv {
#[extends(IntReq, ExtHostReq)]
pub struct ExternStrv(pub TStrv);
impl Request for ExternStrv {
type Response = Vec<TStr>;
type Response = Result<Vec<TStr>, StaleTokenError>;
}
/// A substitute for an interned string in serialized datastructures.
@@ -89,3 +92,23 @@ pub struct Sweeped {
pub strings: Vec<TStr>,
pub vecs: Vec<TStrv>,
}
pub trait InternerToken:
Clone + Copy + Debug + Hash + Eq + PartialOrd + Ord + orchid_api_traits::Coding + 'static
{
}
impl<T: Clone + Copy + Debug + Hash + Eq + PartialOrd + Ord + orchid_api_traits::Coding + 'static>
InternerToken for T
{
}
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq, Coding)]
pub struct StaleTokenError(String);
impl StaleTokenError {
pub fn new<T: InternerToken>(tok: T) -> Self { Self(format!("{tok:?}")) }
}
impl fmt::Display for StaleTokenError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "The interned data associated with {} has already been freed", self.0)
}
}
+1 -1
View File
@@ -70,7 +70,7 @@ pub struct ReflexiveRead {
pub path: TStrv,
}
impl Request for ReflexiveRead {
type Response = TokenTree;
type Response = OrcResult<TokenTree>;
}
/// Invalidates a [crate::ReflexiveTreeId] to reclaim the associated resources
+1 -1
View File
@@ -8,5 +8,5 @@ futures = { version = "0.3.32", default-features = false, features = [
"std",
"async-await",
] }
itertools = "0.14.0"
itertools = "0.15.0"
task-local = "0.1.1"
+7
View File
@@ -1,5 +1,6 @@
use std::any::Any;
use std::cell::RefCell;
use std::fmt::Display;
use std::pin::{Pin, pin};
use std::rc::Rc;
use std::task::{Context, Poll, Waker};
@@ -44,7 +45,13 @@ impl Future for Pollable {
}
}
#[derive(Debug)]
pub struct JoinError;
impl Display for JoinError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "The task panicked or was cancelled")
}
}
/// An object that can be used to inspect the state of the task
pub struct Handle<T: 'static> {
+9 -3
View File
@@ -6,6 +6,7 @@ edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-event = "0.2.1"
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
orchid-async-utils = { version = "0.1.0", path = "../orchid-async-utils" }
@@ -17,8 +18,8 @@ futures = { version = "0.3.32", default-features = false, features = [
"std",
"async-await",
] }
hashbrown = "0.17.0"
itertools = "0.14.0"
hashbrown = "0.17.1"
itertools = "0.15.0"
lazy_static = "1.5.0"
never = "0.1.0"
num-traits = "0.2.19"
@@ -26,11 +27,16 @@ orchid-api = { version = "0.1.0", path = "../orchid-api" }
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
ordered-float = "5.3.0"
regex = "1.12.3"
regex = "1.12.4"
rust-embed = "8.11.0"
substack = "1.1.1"
trait-set = "0.3.0"
task-local = "0.1.1"
tokio = { version = "1.52.3", optional = true }
[features]
tokio = ["dep:tokio"]
default = ["tokio"]
[dev-dependencies]
futures = "0.3.32"
+63 -11
View File
@@ -1,11 +1,15 @@
use std::any::Any;
use std::cell::RefCell;
use std::mem;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use crate::api;
use futures::FutureExt;
use futures::future::LocalBoxFuture;
use orchid_api::binary::TaskHandleBin;
type WideBox = Box<dyn Future<Output = ()>>;
use crate::{DynTaskHandle, api};
static OWNED_VTABLE: RawWakerVTable = RawWakerVTable::new(
|data| {
@@ -56,13 +60,19 @@ static BORROWED_VTABLE: RawWakerVTable = RawWakerVTable::new(
/// 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 wide_box = Box::new(fut) as Box<dyn Future<Output = Fut::Output>>;
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 _) })
mem::drop(unsafe { Box::<Box<dyn Future<Output = ()>>>::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()) };
extern "C" fn poll(
raw: *const (),
cx: api::binary::FutureContextBin,
) -> api::binary::UnitPollBin {
let mut this = unsafe {
let ptr = raw as *mut Box<dyn Future<Output = ()>>;
Pin::new_unchecked(&mut **ptr.as_mut().unwrap())
};
loop {
let mut go_around = false;
let borrowed_waker = unsafe {
@@ -73,11 +83,11 @@ pub fn future_to_vt<Fut: Future<Output = ()> + 'static>(fut: Fut) -> api::binary
};
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 let Poll::Ready(()) = result {
break api::binary::UnitPollBin::Ready;
}
if !go_around {
break api::binary::UnitPoll::Pending;
break api::binary::UnitPollBin::Pending;
}
}
}
@@ -108,8 +118,8 @@ impl Future for VirtualFuture {
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(()),
api::binary::UnitPollBin::Pending => Poll::Pending,
api::binary::UnitPollBin::Ready => Poll::Ready(()),
}
}
}
@@ -120,3 +130,45 @@ impl Drop for VirtualFuture {
/// 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 } }
pub struct VirtualTaskHandle {
dropped: bool,
bin: api::binary::TaskHandleBin,
data: Rc<RefCell<Option<Box<dyn Any>>>>,
}
impl DynTaskHandle for VirtualTaskHandle {
fn abort(mut self: Box<Self>) {
(self.bin.abort)(self.bin.data);
self.dropped = true;
}
fn join(mut self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn Any>> {
Box::pin(async move {
self.dropped = true;
vt_to_future((self.bin.join)(self.bin.data)).await;
self.data.take().expect("Task's future settled but return value has not been initialized")
})
}
}
impl Drop for VirtualTaskHandle {
fn drop(&mut self) {
if !self.dropped {
(self.bin.drop)(self.bin.data)
}
}
}
pub struct FutureOutboxHandle {
data: Rc<RefCell<Option<Box<dyn Any>>>>,
}
impl FutureOutboxHandle {
pub fn into_task_handle(self, bin: TaskHandleBin) -> VirtualTaskHandle {
VirtualTaskHandle { dropped: false, bin, data: self.data }
}
}
pub fn future_to_outbox<Fut: Future<Output = Box<dyn Any>>>(
fut: Fut,
) -> (impl Future<Output = ()>, FutureOutboxHandle) {
let data = Rc::new(RefCell::new(None));
let hand = FutureOutboxHandle { data: data.clone() };
(fut.map(move |o| *data.borrow_mut() = Some(o)), hand)
}
+4 -2
View File
@@ -79,7 +79,9 @@ impl OrcErr {
}
async fn from_api(api: api::OrcError) -> Self {
Self {
description: es(api.description).await,
description: es(api.description)
.await
.expect("Error token arrived after description was freed"),
message: api.message,
positions: join_all(api.locations.into_iter().map(ErrPos::from_api)).await,
}
@@ -268,7 +270,7 @@ pub async fn os_str_to_string<I: Into<ErrPos>>(
match str.to_str() {
Some(str) => Ok(str),
None => Err(mk_errv(
is("Non-unicode string").await,
is!("Non-unicode string"),
format!("{str:?} is not representable as unicode"),
posv,
)),
+5 -1
View File
@@ -35,7 +35,11 @@ impl<T> IdStore<T> {
*rec = Rec::Val(value);
id
}
pub fn add_with(&mut self, cb: impl FnOnce(usize) -> T) -> usize { self.add(cb(self.first)) }
pub fn add_with<R>(&mut self, cb: impl FnOnce(usize) -> (T, R)) -> R {
let (t, r) = cb(self.first);
self.add(t);
r
}
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 {
+16 -10
View File
@@ -105,11 +105,11 @@ 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>;
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, Result<IStr, api::StaleTokenError>>;
/// 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>;
fn ev(&self, t: api::TStrv) -> LocalBoxFuture<'_, Result<IStrv, api::StaleTokenError>>;
}
task_local! {
@@ -128,6 +128,12 @@ fn get_interner() -> Rc<dyn InternerSrv> {
/// Intern a `String` (find its ID or assign it a new one)
pub async fn is(v: &str) -> IStr { get_interner().is(v).await }
#[macro_export]
macro_rules! is {
($str:literal) => {
$crate::tl_cache!(async $crate::IStr: $crate::is($str).await)
};
}
/// 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 }
/// Find a live [IStr] by its ID
@@ -136,7 +142,7 @@ pub async fn iv(v: &[IStr]) -> IStrv { get_interner().iv(v).await }
///
/// 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 }
pub async fn es(v: api::TStr) -> Result<IStr, api::StaleTokenError> { get_interner().es(v).await }
/// Find a live [IStrv] by its ID
///
/// # Panics
@@ -144,7 +150,7 @@ pub async fn es(v: api::TStr) -> IStr { get_interner().es(v).await }
/// 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 async fn ev(v: api::TStrv) -> Result<IStrv, api::StaleTokenError> { get_interner().ev(v).await }
/// Basic engine for an interner that supports recovering if a token is not
/// found locally.
@@ -160,7 +166,6 @@ pub mod local_interner {
use futures::future::LocalBoxFuture;
use hashbrown::hash_table::{Entry, OccupiedEntry, VacantEntry};
use hashbrown::{DefaultHashBuilder, HashTable};
use orchid_api_traits::Coding;
use super::{IStr, IStrHandle, IStrv, IStrvHandle, InternerSrv};
use crate::api;
@@ -169,7 +174,7 @@ pub mod local_interner {
/// vector interning
pub trait InternableCard: 'static + Sized + Default + Debug {
/// API representation of an interner key
type Token: Clone + Copy + Debug + Hash + Eq + PartialOrd + Ord + Coding + 'static;
type Token: api::InternerToken;
/// Owned version of interned value physically held by `'static` interner
/// and token
type Data: 'static + Borrow<Self::Borrow> + Eq + Hash + Debug;
@@ -348,6 +353,7 @@ pub mod local_interner {
},
}
}
pub fn to_stale_token(&self) -> api::StaleTokenError { api::StaleTokenError::new(self.token) }
}
impl<B: InternableCard> Debug for ExternError<'_, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -405,8 +411,8 @@ pub mod local_interner {
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 es(&self, t: api::TStr) -> LocalBoxFuture<'_, Result<IStr, api::StaleTokenError>> {
Box::pin(future::ready(self.str.e(t).map_err(|e| e.to_stale_token())))
}
fn iv<'a>(&'a self, v: &'a [IStr]) -> LocalBoxFuture<'a, IStrv> {
match self.strv.i(v) {
@@ -414,8 +420,8 @@ pub mod local_interner {
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")))
fn ev(&self, t: api::TStrv) -> LocalBoxFuture<'_, Result<IStrv, api::StaleTokenError>> {
Box::pin(future::ready(self.strv.e(t).map_err(|e| e.to_stale_token())))
}
}
+6 -3
View File
@@ -7,9 +7,12 @@ impl<'a, I: Iterator<Item = E> + Clone, E: fmt::Display> fmt::Display for PrintL
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (pos, item) in self.0.clone().with_position() {
match pos {
Position::First | Position::Only => write!(f, "{item}")?,
Position::Middle => write!(f, ", {item}")?,
Position::Last => write!(f, ", {} {item}", self.1)?,
// first or only item
Position { is_first: true, .. } => write!(f, "{item}")?,
// last but not only item
Position { is_last: true, .. } => write!(f, ", {} {item}", self.1)?,
// middle item
_ => write!(f, ", {item}")?,
}
}
Ok(())
+2
View File
@@ -1,6 +1,8 @@
pub use async_once_cell;
use orchid_api as api;
mod spawn;
pub use spawn::*;
mod on_drop;
pub use on_drop::*;
mod binary;
+11 -3
View File
@@ -157,7 +157,10 @@ impl SrcRange {
pub fn suffix(&self) -> Self { Self::zw(self.path(), self.end()) }
/// Deserialize from a message
pub async fn from_api(api: &api::SourceRange) -> Self {
Self { path: Sym::from_api(api.path).await, range: api.range.clone() }
Self {
path: Sym::from_api(api.path).await.expect("Source path should be statically interned"),
range: api.range.clone(),
}
}
/// Serialize to a message
pub fn to_api(&self) -> api::SourceRange {
@@ -192,7 +195,7 @@ pub struct CodeGenInfo {
}
impl CodeGenInfo {
/// A codegen marker with no user message and parameters
pub async fn new_short(generator: Sym) -> Self { Self { generator, details: is("").await } }
pub async fn new_short(generator: Sym) -> Self { Self { generator, details: is!("") } }
/// A codegen marker with a user message or parameters
pub async fn new_details(generator: Sym, details: impl AsRef<str>) -> Self {
Self { generator, details: is(details.as_ref()).await }
@@ -201,7 +204,12 @@ impl CodeGenInfo {
pub fn pos(&self) -> Pos { Pos::Gen(self.clone()) }
/// Deserialize from a message
pub async fn from_api(api: &api::CodeGenInfo) -> Self {
Self { generator: Sym::from_api(api.generator).await, details: es(api.details).await }
Self {
generator: Sym::from_api(api.generator)
.await
.expect("Generator path should be statically interned"),
details: es(api.details).await.expect("Generator label should be statically interned"),
}
}
/// Serialize to a message
pub fn to_api(&self) -> api::CodeGenInfo {
+17 -18
View File
@@ -117,7 +117,12 @@ impl VName {
if data.is_empty() { Err(EmptyNameError) } else { Ok(Self(data)) }
}
pub async fn deintern(name: impl IntoIterator<Item = api::TStr>) -> Result<Self, EmptyNameError> {
Self::new(join_all(name.into_iter().map(es)).await)
Self::new(
join_all(
name.into_iter().map(|n| async move { es(n).await.expect("Name segment is stale") }),
)
.await,
)
}
/// Unwrap the enclosed vector
pub fn into_vec(self) -> Vec<IStr> { self.0 }
@@ -212,8 +217,8 @@ impl Sym {
/// Extern the sym for editing
pub fn to_vname(&self) -> VName { VName(self[..].to_vec()) }
/// Decode from a message
pub async fn from_api(marker: api::TStrv) -> Sym {
Self::from_tok(ev(marker).await).expect("Empty sequence found for serialized Sym")
pub async fn from_api(marker: api::TStrv) -> Result<Sym, api::StaleTokenError> {
Ok(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() }
@@ -295,11 +300,11 @@ macro_rules! sym {
($seg1:tt $( :: $seg:tt)*) => {
$crate::tl_cache!(async $crate::Sym : {
$crate::Sym::from_tok(
$crate::iv(&[
$crate::is($crate::sym!(@SEG $seg1)).await
$( , $crate::is($crate::sym!(@SEG $seg)).await )*
])
.await
$crate::iv(&[
$crate::is($crate::sym!(@SEG $seg1)).await
$( , $crate::is($crate::sym!(@SEG $seg)).await )*
])
.await
).unwrap()
})
};
@@ -369,20 +374,14 @@ mod test {
spin_on(with_interner(local_interner(), async {
assert_eq!(
sym!(foo::bar::baz),
Sym::new([is("foo").await, is("bar").await, is("baz").await]).await.unwrap()
Sym::new([is!("foo"), is!("bar"), is!("baz")]).await.unwrap()
);
assert_eq!(
sym!(foo::bar::[|>]),
Sym::new([is("foo").await, is("bar").await, is("|>").await]).await.unwrap()
);
assert_eq!(
vname!(foo::bar::baz),
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])
Sym::new([is!("foo"), is!("bar"), is!("|>")]).await.unwrap()
);
assert_eq!(vname!(foo::bar::baz), VName::new([is!("foo"), is!("bar"), is!("baz")]).unwrap());
assert_eq!({ vpath!(foo::bar::baz) }, VPath::new([is!("foo"), is!("bar"), is!("baz")]));
}))
}
}
+1 -1
View File
@@ -54,7 +54,7 @@ pub struct NumError {
pub async fn num_to_errv(NumError { kind, range }: NumError, offset: u32, source: &Sym) -> OrcErrv {
mk_errv(
is("Failed to parse number").await,
is!("Failed to parse number"),
match kind {
NumErrorKind::NaN => "NaN emerged during parsing",
NumErrorKind::InvalidDigit => "non-digit character encountered",
+15 -14
View File
@@ -123,7 +123,10 @@ pub struct Comment {
}
impl Comment {
pub async fn from_api(c: &api::Comment, src: Sym) -> Self {
Self { text: es(c.text).await, sr: SrcRange::new(c.range.clone(), &src) }
Self {
text: es(c.text).await.expect("Comment text should be statically interned"),
sr: SrcRange::new(c.range.clone(), &src),
}
}
pub async fn from_tk(tk: &TokTree<impl ManagedStrat>) -> Option<Self> {
match &tk.tok {
@@ -182,21 +185,19 @@ pub async fn try_pop_no_fluff<'a, M: ManagedStrat>(
) -> ParseRes<'a, &'a TokTree<M>, M> {
match snip.skip_fluff().split_first() {
Some((output, tail)) => Ok(Parsed { output, tail }),
None =>
Err(mk_errv(is("Unexpected end").await, "Line ends abruptly; more tokens were expected", [
snip.sr(),
])),
None => Err(mk_errv(is!("Unexpected end"), "Line ends abruptly; more tokens were expected", [
snip.sr(),
])),
}
}
/// Fail if the snippet isn't empty
pub async fn expect_end(snip: Snippet<'_, impl ManagedStrat>) -> OrcRes<()> {
match snip.skip_fluff().get(0) {
Some(surplus) => Err(mk_errv(
is("Extra code after end of line").await,
"Code found after the end of the line",
[surplus.sr.pos()],
)),
Some(surplus) =>
Err(mk_errv(is!("Extra code after end of line"), "Code found after the end of the line", [
surplus.sr.pos(),
])),
None => Ok(()),
}
}
@@ -210,7 +211,7 @@ pub async fn expect_tok<'a, M: ManagedStrat>(
match &head.tok {
Token::Name(n) if *n == tok => Ok(Parsed { output: (), tail }),
t => Err(mk_errv(
is("Expected specific keyword").await,
is!("Expected specific keyword"),
format!("Expected {tok} but found {:?}", fmt(t).await),
[head.sr()],
)),
@@ -243,7 +244,7 @@ pub async fn parse_multiname<'a, M: ManagedStrat>(
) -> ParseRes<'a, Vec<Import>, M> {
let Some((tt, tail)) = tail.skip_fluff().split_first() else {
return Err(mk_errv(
is("Expected token").await,
is!("Expected token"),
"Expected a name, a parenthesized list of names, or a globstar.",
[tail.sr().pos()],
));
@@ -257,7 +258,7 @@ pub async fn parse_multiname<'a, M: ManagedStrat>(
match &tt.tok {
Token::NS(ns, body) => {
if !ns.starts_with(name_start) {
report(mk_errv(is("Unexpected name prefix").await, "Only names can precede ::", [ttpos]))
report(mk_errv(is!("Unexpected name prefix"), "Only names can precede ::", [ttpos]))
};
let out = Box::pin(rec(body)).await?;
Ok(out.into_iter().update(|i| i.0.push(ns.clone())).collect_vec())
@@ -280,7 +281,7 @@ pub async fn parse_multiname<'a, M: ManagedStrat>(
Ok(o)
},
t => Err(mk_errv(
is("Unrecognized name end").await,
is!("Unrecognized name end"),
format!("Names cannot end with {:?} tokens", fmt(t).await),
[ttpos],
)),
+144
View File
@@ -0,0 +1,144 @@
use std::any::Any;
#[cfg(feature = "tokio")]
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use std::time::Duration;
#[cfg(feature = "tokio")]
use async_event::Event;
use futures::future::LocalBoxFuture;
use task_local::task_local;
use crate::on_drop;
/// Handle for a task that gets polled independently from the codepath that
/// spawned it. Wrapper for a [DynTaskHandle]
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() }
}
/// Operations on tasks exposed by the underlying [SpawnSrv]. Wrapped by
/// [TaskHandle]
pub trait DynTaskHandle {
/// It is guaranteed that the future will never be polled after this is called
fn abort(self: Box<Self>);
/// take the future out of the task. If the return value
/// is dropped, the spawned future is also dropped. The returned boxed future
/// will finish any sleep requested at spawn.
fn join(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn Any>>;
}
/// Trait exposed by the platform-specific scheduler for spawning futures and
/// waiting. Use [SpawnExt] for an ergonomic interface
pub trait SpawnSrv {
/// Returns a future that will resolve after the specified time has elapsed.
///
/// The sleep future should not keep the interpreter alive.
fn sleep_obj(&self, delay: Duration) -> LocalBoxFuture<'static, ()>;
/// Spawn a future off-task, but in the same thread.
///
/// The implementor should not exit while there are pending tasks to allow
/// external communication channels to cleanly shut down.
fn spawn_obj(
&self,
keepalive: bool,
fut: LocalBoxFuture<'static, Box<dyn Any>>,
) -> Box<dyn DynTaskHandle>;
}
/// Extension functions to [SpawnSrv] to conceal all the wrapping
pub trait SpawnExt: SpawnSrv {
/// Resolve after some time has elapsed
fn sleep(&self, delay: Duration) -> impl Future<Output = ()> + 'static { self.sleep_obj(delay) }
/// Start a new task on the same thread
fn spawn<F: Future + 'static>(&self, fut: F) -> TaskHandle<F::Output> {
let th_obj = self.spawn_obj(true, Box::pin(async { Box::new(fut.await) as _ }));
TaskHandle(th_obj, PhantomData)
}
/// Start a new task on the same thread
fn spawn_weak<F: Future + 'static>(&self, fut: F) -> TaskHandle<F::Output> {
let th_obj = self.spawn_obj(false, Box::pin(async { Box::new(fut.await) as _ }));
TaskHandle(th_obj, PhantomData)
}
}
impl<T: ?Sized + SpawnSrv> SpawnExt for T {}
task_local! {
static SPAWN: Rc<dyn SpawnSrv>;
}
pub async fn with_spawn<F: Future>(spawn: Rc<dyn SpawnSrv>, fut: F) -> F::Output {
SPAWN.scope(spawn, fut).await
}
pub fn get_spawn() -> Rc<dyn SpawnSrv> { SPAWN.with(|s| s.clone()) }
pub async fn sleep(delay: Duration) { get_spawn().sleep(delay).await }
pub async fn spawn<F: Future + 'static>(fut: F) -> TaskHandle<F::Output> { get_spawn().spawn(fut) }
pub async fn spawn_weak<F: Future + 'static>(fut: F) -> TaskHandle<F::Output> {
get_spawn().spawn_weak(fut)
}
#[cfg(feature = "tokio")]
#[derive(Clone)]
pub struct TokioSpawn {
notifier: Rc<Event>,
task_counter: Rc<RefCell<usize>>,
}
#[cfg(feature = "tokio")]
impl TokioSpawn {
pub fn new() -> Self {
Self { notifier: Rc::new(Event::new()), task_counter: Rc::new(RefCell::new(0)) }
}
pub fn wait_for(&self) -> impl Future<Output = ()> {
self.notifier.wait_until(|| (*self.task_counter.borrow() == 0).then_some(()))
}
}
#[cfg(feature = "tokio")]
impl Default for TokioSpawn {
fn default() -> Self { Self::new() }
}
#[cfg(feature = "tokio")]
impl SpawnSrv for TokioSpawn {
fn sleep_obj(&self, delay: Duration) -> LocalBoxFuture<'static, ()> {
Box::pin(tokio::time::sleep(delay))
}
fn spawn_obj(
&self,
keepalive: bool,
fut: LocalBoxFuture<'static, Box<dyn Any>>,
) -> Box<dyn DynTaskHandle> {
let spawner = self.clone();
let on_task_drop = if keepalive {
*self.task_counter.borrow_mut() += 1;
self.notifier.notify_all();
Some(on_drop(move || {
*spawner.task_counter.borrow_mut() -= 1;
spawner.notifier.notify_all();
}))
} else {
None
};
struct TokioHandle(tokio::task::JoinHandle<Box<dyn Any>>);
return Box::new(TokioHandle(tokio::task::spawn_local(async move {
let r = fut.await;
std::mem::drop(on_task_drop);
r
})));
impl DynTaskHandle for TokioHandle {
fn abort(self: Box<Self>) { self.0.abort(); }
fn join(self: Box<Self>) -> LocalBoxFuture<'static, Box<dyn Any>> {
Box::pin(async { self.0.await.expect("off-task panicked") })
}
}
}
}
+3 -3
View File
@@ -154,13 +154,13 @@ impl<M: ManagedStrat> TokTree<M> {
let pos = SrcRange::new(tt.range, &cx.src);
let tok = match_mapping!(tt.token, api::Token => Token::<M> {
BR,
NS(n => es(n).await,
NS(n => es(n).await.expect("Namespace prefix is stale interned string"),
b => Box::new(Self::from_api(*b, cx).boxed_local().await)),
Bottom(e => OrcErrv::from_api(e).await),
LambdaHead(arg => Box::new(Self::from_api(*arg, cx).boxed_local().await)),
Name(n => es(n).await),
Name(n => es(n).await.expect("Name is stale interned string")),
S(par, b => ttv_from_api(b, cx).await),
Comment(c => es(c).await),
Comment(c => es(c).await.expect("Comment is stale interned string")),
NewExpr(expr => M::Extra::from_api(expr, cx.extra, pos.clone()).await),
Handle(tk => M::Expr::from_api(tk, cx.handle, pos.clone()).await),
Reflexive(l => M::Flex::from_api(l, cx.lazy, pos.clone()).await)
+7 -7
View File
@@ -9,7 +9,7 @@ edition = "2024"
async-event = "0.2.1"
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
async-once-cell = "0.5.4"
chrono = "0.4.44"
chrono = "0.4.45"
derive_destructure = "1.0.0"
dyn-clone = "1.0.20"
futures = { version = "0.3.32", default-features = false, features = [
@@ -17,9 +17,9 @@ futures = { version = "0.3.32", default-features = false, features = [
"async-await",
] }
futures-locks = "0.7.1"
hashbrown = "0.17.0"
hashbrown = "0.17.1"
include_dir = { version = "0.7.4", optional = true }
itertools = "0.14.0"
itertools = "0.15.0"
konst = "0.4.3"
lazy_static = "1.5.0"
memo-map = "0.3.3"
@@ -29,17 +29,17 @@ orchid-api = { version = "0.1.0", path = "../orchid-api" }
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
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", default-features = false }
ordered-float = "5.3.0"
pastey = "0.2.2"
pastey = "0.2.3"
substack = "1.1.1"
task-local = "0.1.1"
tokio = { version = "1.52.1", optional = true, features = ["io-std", "time"] }
tokio = { version = "1.52.3", optional = true, features = ["io-std", "time"] }
tokio-util = { version = "0.7.18", optional = true, features = ["compat"] }
trait-set = "0.3.0"
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
[features]
tokio = ["dep:tokio", "dep:tokio-util"]
tokio = ["dep:tokio", "dep:tokio-util", "orchid-base/tokio"]
default = ["tokio"]
+5 -5
View File
@@ -158,7 +158,7 @@ impl NotTypAtom {
/// Convert to a generic Orchid error
pub async fn mk_err(&self) -> OrcErrv {
mk_errv(
is("Not the expected type").await,
is!("Not the expected type"),
format!("The expression {} is not a {}", fmt(&self.expr).await, self.typ),
[self.pos.clone()],
)
@@ -367,7 +367,7 @@ impl Format for AtomFactory {
/// Error produced when an atom can not be applied to a value as a function
pub async fn err_not_callable(unit: &FmtUnit) -> OrcErrv {
mk_errv_floating(
is("This atom is not callable").await,
is!("This atom is not callable"),
format!("Attempted to apply {} as function", take_first(unit, false)),
)
}
@@ -375,13 +375,13 @@ pub async fn err_not_callable(unit: &FmtUnit) -> OrcErrv {
/// Error produced when an atom can not be the final value of the program
pub async fn err_not_command(unit: &FmtUnit) -> OrcErrv {
mk_errv_floating(
is("This atom is not a command").await,
is!("This atom is not a command"),
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 }
pub(crate) async fn err_exit_success_msg() -> IStr { is!("Early successful exit") }
pub(crate) async fn err_exit_failure_msg() -> IStr { is!("Early failure exit") }
/// Sentinel error returnable from [crate::OwnedAtom::command] or
/// [crate::ThinAtom::command] to indicate that the program should exit with a
+23 -15
View File
@@ -2,7 +2,7 @@ use std::rc::Rc;
use std::time::Duration;
use futures::future::LocalBoxFuture;
use orchid_base::future_to_vt;
use orchid_base::{SpawnExt, SpawnSrv, future_to_outbox, future_to_vt, vt_to_future};
use crate::{ExtPort, ExtensionBuilder, api};
@@ -12,24 +12,32 @@ 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))
impl SpawnSrv for Spawner {
fn sleep_obj(&self, delay: Duration) -> LocalBoxFuture<'static, ()> {
Box::pin(vt_to_future((self.0.sleep)(self.0.data, delay.as_millis().try_into().unwrap())))
}
fn spawn_obj(
&self,
keepalive: bool,
fut: LocalBoxFuture<'static, Box<dyn std::any::Any>>,
) -> Box<dyn orchid_base::DynTaskHandle> {
let (future, outbox) = future_to_outbox(fut);
let hand_bin = (self.0.spawn)(self.0.data, keepalive, future_to_vt(future));
Box::new(outbox.into_task_handle(hand_bin))
}
}
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)),
})),
);
let spawn = Rc::new(Spawner(cx.spawner));
let spawn2 = spawn.clone();
// TODO: define safe binary spawner interface and implement user side.
// Take note that
spawn2.spawn(builder.run(ExtPort {
input: Box::pin(cx.input),
output: Box::pin(cx.output),
log: Box::pin(cx.log),
spawn,
}));
}
/// Generate entrypoint for the dylib extension loader
+1 -1
View File
@@ -32,7 +32,7 @@ impl<T: TryFromExpr, U: TryFromExpr> TryFromExpr for (T, U) {
/// Error raised when a composite expression was assumed to be an
/// [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])
mk_errv(is!("Expected an atom"), format!("{} is not an atom", fmt(value).await), [pos])
}
impl TryFromExpr for ForeignAtom {
+2 -2
View File
@@ -62,12 +62,12 @@ impl OwnedAtom for Replier {
/// 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: for<'a> AsyncFnOnce(ExecHandle<'a>) -> R + 'static>(f: F) -> GExpr {
let (cmd_snd, cmd_recv) = channel(0);
let halt =
async { Command::Halt(f(ExecHandle(cmd_snd, PhantomData)).await.to_gen().await) }.into_stream();
let coro = BuilderCoroutine(Rc::new(BuilderCoroutineData {
name: type_name::<R>(),
name: type_name::<F>(),
receiver: Mutex::new(stream::select(halt, cmd_recv).boxed_local()),
}));
coro.run().await
+24 -60
View File
@@ -1,24 +1,20 @@
use std::any::Any;
use std::cell::RefCell;
use std::future::Future;
use std::marker::PhantomData;
use std::mem;
use std::num::NonZero;
use std::pin::Pin;
use std::rc::Rc;
use std::time::Duration;
use futures::future::{LocalBoxFuture, join_all, join3};
use futures::{AsyncReadExt, AsyncWriteExt, StreamExt, stream};
use hashbrown::HashMap;
use itertools::Itertools;
use orchid_api_traits::{Decode, Encode, Request, UnderRoot, enc_vec};
use orchid_async_utils::{Handle, JoinError, to_task};
use orchid_base::{
Client, ClientExt, CommCx, Comment, IoComm, MsgReader, MsgReaderExt, ReqHandleExt, ReqReaderExt,
Snippet, Sym, TokFromApiCx, TokToApiCx, TokenVariant, Witness, char_filter_match,
char_filter_union, es, io_comm, is, log, mk_char_filter, try_with_reporter, ttv_from_api,
ttv_into_api, with_interner, with_logger, with_stash,
ttv_into_api, with_interner, with_logger, with_reporter, with_spawn, with_stash,
};
use substack::Substack;
use task_local::task_local;
@@ -106,46 +102,6 @@ impl<F: AsyncFnOnce(LocalBoxFuture<'_, ()>) + 'static> ContextModifier for F {
Box::pin((self)(fut))
}
}
pub(crate) trait DynTaskHandle: 'static {
fn abort(self: Box<Self>);
fn join(self: Box<Self>) -> LocalBoxFuture<'static, Result<Box<dyn Any>, JoinError>>;
}
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) -> Result<T, JoinError> { Ok(*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, Result<Box<dyn Any>, JoinError>> {
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
@@ -293,8 +249,8 @@ impl ExtensionBuilder {
},
api::HostExtReq::LexExpr(lex @ api::LexExpr { sys, src, text, pos, id }) =>
with_sys_record(sys, async {
let text = es(text).await;
let src = Sym::from_api(src).await;
let text = es(text).await.expect("Server did not retain lexable text");
let src = Sym::from_api(src).await.expect("Server did not retain lexable path");
let expr_store = BorrowedExprStore::new();
let tail = &text[pos as usize..];
let trigger_char = tail.chars().next().unwrap();
@@ -317,7 +273,6 @@ impl ExtensionBuilder {
})
.await;
let pos = (text.len() - s.len()) as u32;
eprintln!("Lexation up to pos {pos} ({} - {})", text.len(), s.len());
expr_store.dispose().await;
return handle.reply(&lex, Some(Ok(api::LexedExpr { pos, expr }))).await;
},
@@ -333,7 +288,7 @@ impl ExtensionBuilder {
let api::ParseLine { module, src, exported, comments, sys, line, idx } = pline;
with_sys_record(sys, async {
let parsers = dyn_cted().inst().dyn_parsers();
let src = Sym::from_api(src).await;
let src = Sym::from_api(src).await.expect("Server did not retain parsable path");
let comments =
join_all(comments.iter().map(|c| Comment::from_api(c, src.clone()))).await;
let expr_store = BorrowedExprStore::new();
@@ -342,7 +297,9 @@ impl ExtensionBuilder {
let line: Vec<GenTokTree> = ttv_from_api(line, &mut cx).await;
let snip = Snippet::new(line.first().expect("Empty line"), &line);
let parser = parsers[idx as usize];
let module = Sym::from_api(module).await;
let module = Sym::from_api(module)
.await
.expect("Server did not retain enclosing module path for parsing");
let pctx = ParsCtx::new(module);
let o_line =
match try_with_reporter(parser.parse(pctx, exported, comments, snip)).await {
@@ -357,9 +314,18 @@ impl ExtensionBuilder {
},
api::HostExtReq::ReflexiveRead(ref rr @ api::ReflexiveRead { tree, path }) =>
with_sys_record(tree.sys, async {
let tt = read_flex_tree(tree.id, Sym::from_api(path).await).await;
let mut cx = TokToApiCx { extra: &mut (), handle: &mut (), lazy: &mut () };
handle.reply(rr, tt.into_api(&mut cx).await).await
let path = Sym::from_api(path)
.await
.expect("Server did not retain path for flex tree query");
handle
.reply(rr, match with_reporter(read_flex_tree(tree.id, path)).await {
Ok(tt) => {
let mut cx = TokToApiCx { extra: &mut (), handle: &mut (), lazy: &mut () };
Ok(tt.into_api(&mut cx).await)
},
Err(err) => Err(err.to_api()),
})
.await
})
.await,
api::HostExtReq::ReflexiveDrop(ref rd @ api::ReflexiveDrop(tree)) =>
@@ -393,7 +359,9 @@ impl ExtensionBuilder {
let (mut req_in, req) = pipe(1024);
let (rep, mut rep_out) = pipe(1024);
let mut reply = Vec::new();
let key = Sym::from_api(*key).await;
let key = Sym::from_api(*key)
.await
.expect("Server did not retain forwarded request key");
let (.., some) = join3(
async { req_in.write_all(payload).await.expect("Ingress failed") },
async { rep_out.read_to_end(&mut reply).await.expect("Egress failed") },
@@ -456,12 +424,8 @@ impl ExtensionBuilder {
with_comm(
Rc::new(client),
comm_ctx,
SPAWN.scope(
Rc::new(move |delay, fut| {
let (poll, handle) = to_task(fut);
spawn(delay, Box::pin(poll));
Box::new(handle)
}),
with_spawn(
spawn,
(self.context.into_iter()).fold(
Box::pin(async { extension_fut.await.unwrap() }) as LocalBoxFuture<()>,
|fut, cx| cx.apply(fut),
+2 -3
View File
@@ -1,13 +1,12 @@
use std::pin::Pin;
use std::rc::Rc;
use std::time::Duration;
use futures::future::LocalBoxFuture;
use futures::{AsyncRead, AsyncWrite};
use orchid_base::SpawnSrv;
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, ()>)>,
pub spawn: Rc<dyn SpawnSrv>,
}
+2 -1
View File
@@ -145,7 +145,8 @@ impl OwnedAtom for Fun {
self.args.clone()
}
async fn deserialize(mut ds_cx: impl DeserializeCtx, args: Self::Refs) -> Self {
let path = Sym::from_api(ds_cx.decode().await).await;
let path =
Sym::from_api(ds_cx.decode().await).await.expect("Server does not have this function's path");
let record = (FUNS_CTX.with(|funs| funs.borrow().get(&(sys_id(), path.clone())).cloned()))
.expect("Function missing during deserialization")
.clone();
+1 -1
View File
@@ -141,7 +141,7 @@ pub fn flex_tok<F: AsyncFn(Sym) -> GenTokTree + 'static>(f: F) -> GenTok {
pub async fn resolve_name(path: &Sym, local: &Sym) -> OrcRes<Sym> {
match request(api::ResolveName { sys: sys_id(), path: path.to_api(), name: local.to_api() }).await
{
Ok(sym) => Ok(Sym::from_api(sym).await),
Ok(sym) => Ok(Sym::from_api(sym).await.expect("Server should cache this local for the lifetime of the extension")),
Err(e) => Err(OrcErrv::from_api(e).await),
}
}
+22 -8
View File
@@ -1,7 +1,10 @@
use std::rc::Rc;
use futures::future::{LocalBoxFuture, join_all, ready};
use async_fn_stream::try_stream;
use futures::TryStreamExt;
use futures::future::{LocalBoxFuture, ready};
use itertools::Itertools;
use orchid_api::StaleTokenError;
use orchid_base::local_interner::{Int, StrBranch, StrvBranch};
use orchid_base::{IStr, IStrv, InternerSrv};
@@ -20,10 +23,11 @@ impl InternerSrv for ExtInterner {
Box::pin(async { e.set_if_empty(mute_reply(request(api::InternStr(v.to_owned()))).await) }),
}
}
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, IStr> {
fn es(&self, t: api::TStr) -> LocalBoxFuture<'_, Result<IStr, api::StaleTokenError>> {
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)) }),
Ok(i) => Box::pin(ready(Ok(i))),
Err(e) =>
Box::pin(async move { Ok(e.set_if_empty(Rc::new(request(api::ExternStr(t)).await?))) }),
}
}
fn iv<'a>(&'a self, v: &'a [IStr]) -> LocalBoxFuture<'a, IStrv> {
@@ -34,12 +38,22 @@ impl InternerSrv for ExtInterner {
}),
}
}
fn ev(&self, t: orchid_api::TStrv) -> LocalBoxFuture<'_, IStrv> {
fn ev(&self, t: api::TStrv) -> LocalBoxFuture<'_, Result<IStrv, StaleTokenError>> {
match self.strv.e(t) {
Ok(i) => Box::pin(ready(i)),
Ok(i) => Box::pin(ready(Ok(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))
Ok(
e.set_if_empty(Rc::new(
try_stream(async |mut h| {
for tstr in request(api::ExternStrv(t)).await? {
h.emit(self.es(tstr).await?).await
}
Ok(h)
})
.try_collect()
.await?,
)),
)
}),
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ use crate::gen_tok::GenTokTree;
use crate::{BorrowedExprStore, api, request};
pub(crate) async fn ekey_not_applicable() -> IStr {
is("Pseudo-error to communicate that the current branch in a dispatch doesn't apply").await
is!("Pseudo-error to communicate that the current branch in a dispatch doesn't apply")
}
const MSG_INTERNAL_ERROR: &str = "This error is a sentinel for the extension library.\
it should not be emitted by the extension.";
+1 -1
View File
@@ -59,7 +59,7 @@ impl ReflMod {
Ok(details) => details,
};
for (k, v) in reply.members {
let k = es(k).await;
let k = es(k).await.expect("Stale token for the name of a member in the tree");
let mem = match self.0.members.get(&k) {
Some(mem) => mem,
None => {
+8 -5
View File
@@ -41,19 +41,22 @@ impl OwnedAtom for WriterAtom {
}
impl Supports<OutputReq> for WriterAtom {
async fn handle(&self, hand: Box<dyn ReqHandle>, req: OutputReq) -> Result<Receipt> {
match req {
OutputReq::WriteReq(ref wr @ WriteReq { ref data }) => {
eprintln!("output req {req:?}");
let reply = match &req {
OutputReq::WriteReq(wr @ WriteReq { data }) => {
self.0.lock().await.buf.extend(data);
hand.reply(wr, Ok(())).await
},
OutputReq::FlushReq(ref fr @ FlushReq) => {
OutputReq::FlushReq(fr @ FlushReq) => {
let mut g = self.0.lock().await;
let WriterState { buf, writer } = &mut *g;
hand.reply(fr, writer.write_all(&buf[..]).await.map_err(|e| e.into())).await
},
OutputReq::CloseReq(ref cr @ CloseReq) =>
OutputReq::CloseReq(cr @ CloseReq) =>
hand.reply(cr, self.0.lock().await.writer.close().await.map_err(|e| e.into())).await,
}
};
eprintln!("output rep {req:?}");
reply
}
}
+1
View File
@@ -68,6 +68,7 @@ pub(crate) fn general_atoms() -> impl Iterator<Item = Option<Box<dyn AtomOps>>>
Some(WriterAtom::ops()),
Some(ReaderAtom::ops()),
Some(IoError::ops()),
Some(<()>::ops()),
]
.into_iter()
}
+11 -37
View File
@@ -10,48 +10,22 @@ use crate::{ExtPort, ExtensionBuilder};
/// shutdown.
#[cfg(feature = "tokio")]
pub async fn __tokio_entrypoint(builder: ExtensionBuilder) {
use std::cell::RefCell;
use async_event::Event;
use orchid_base::{SpawnExt, TokioSpawn};
use tokio::io::{stderr, stdin, stdout};
use tokio::task::{LocalSet, spawn_local};
use tokio::task::LocalSet;
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
let cc = Rc::new(Event::new());
let c = Rc::new(RefCell::new(1));
LocalSet::new()
.run_until(async {
let counter = (c.clone(), cc.clone());
builder
.run(ExtPort {
input: Box::pin(stdin().compat()),
output: Box::pin(stdout().compat_write()),
log: Box::pin(stderr().compat_write()),
spawn: Rc::new(move |delay, fut| {
let (c, cc) = counter.clone();
if delay.is_zero() {
*c.borrow_mut() += 1;
cc.notify_all();
spawn_local(async move {
fut.await;
*c.borrow_mut() -= 1;
cc.notify_all();
});
} else {
let at = tokio::time::Instant::now() + delay;
spawn_local(async move {
tokio::time::sleep_until(at).await;
*c.borrow_mut() += 1;
cc.notify_all();
fut.await;
*c.borrow_mut() -= 1;
cc.notify_all();
});
}
}),
})
.await;
cc.wait_until(|| (*c.borrow() == 0).then_some(())).await;
let spawn = Rc::new(TokioSpawn::new());
let builder_handle = spawn.spawn(builder.run(ExtPort {
input: Box::pin(stdin().compat()),
output: Box::pin(stdout().compat_write()),
log: Box::pin(stderr().compat_write()),
spawn: spawn.clone(),
}));
builder_handle.join().await;
spawn.wait_for().await;
})
.await;
}
+7 -8
View File
@@ -40,7 +40,7 @@ pub fn module(
name: &str,
mems: impl IntoIterator<Item = Vec<GenMember>>,
) -> Vec<GenMember> {
let (name, kind) = (name.to_string(), LazyMemKind::Mod(mems.into_iter().flatten().collect()));
let (name, kind) = (name.to_string(), LazyMemKind::Mod(merge_trivial(mems)));
vec![GenMember { name, kind, public, comments: vec![] }]
}
/// A Rust function that is passed to Orchid via [Fun]
@@ -72,25 +72,24 @@ pub fn comments<'a>(
/// Trivially merge a gen tree. Behaviours were chosen to make this simple.
///
/// - Comments on imports are discarded
/// - Comments on exports and submodules are combined
/// - Duplicate constants result in an error
/// - A combination of lazy and anything results in an error
pub fn merge_trivial(trees: impl IntoIterator<Item = Vec<GenMember>>) -> Vec<GenMember> {
let mut all_members = HashMap::<String, (LazyMemKind, Vec<String>)>::new();
let mut all_members = HashMap::<String, (bool, LazyMemKind, Vec<String>)>::new();
for mem in trees.into_iter().flatten() {
assert!(mem.public, "Non-trivial merge in {}", mem.name);
match mem.kind {
unit @ (LazyMemKind::Const(_) | LazyMemKind::Lazy(_)) => {
let prev = all_members.insert(mem.name.clone(), (unit, mem.comments.into_iter().collect()));
let prev = all_members
.insert(mem.name.clone(), (mem.public, unit, mem.comments.into_iter().collect()));
assert!(prev.is_none(), "Conflict in trivial tree merge on {}", mem.name);
},
LazyMemKind::Mod(members) => match all_members.entry(mem.name.clone()) {
hashbrown::hash_map::Entry::Vacant(slot) => {
slot.insert((LazyMemKind::Mod(members), mem.comments.into_iter().collect()));
slot.insert((mem.public, LazyMemKind::Mod(members), mem.comments.into_iter().collect()));
},
hashbrown::hash_map::Entry::Occupied(mut old) => match old.get_mut() {
(LazyMemKind::Mod(old_items), old_cmts) => {
(old_pub, LazyMemKind::Mod(old_items), old_cmts) if *old_pub == mem.public => {
let mut swap = vec![];
std::mem::swap(&mut swap, old_items);
*old_items = merge_trivial([swap, members]);
@@ -103,7 +102,7 @@ pub fn merge_trivial(trees: impl IntoIterator<Item = Vec<GenMember>>) -> Vec<Gen
}
(all_members.into_iter())
.map(|(name, (kind, comments))| GenMember { comments, kind, name, public: true })
.map(|(name, (public, kind, comments))| GenMember { comments, kind, name, public })
.collect_vec()
}
+7 -7
View File
@@ -10,12 +10,12 @@ async-event = "0.2.1"
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
async-once-cell = "0.5.4"
bound = "0.6.0"
chrono = "0.4.44"
chrono = "0.4.45"
derive_destructure = "1.0.0"
futures = { version = "0.3.32", features = ["std"], default-features = false }
futures-locks = "0.7.1"
hashbrown = "0.17.0"
itertools = "0.14.0"
hashbrown = "0.17.1"
itertools = "0.15.0"
lazy_static = "1.5.0"
libloading = { version = "0.9.0", optional = true }
memo-map = "0.3.3"
@@ -24,17 +24,17 @@ num-traits = "0.2.19"
orchid-api = { version = "0.1.0", path = "../orchid-api" }
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", default-features = false }
orchid-extension = { version = "0.1.0", path = "../orchid-extension", optional = true }
ordered-float = "5.3.0"
pastey = "0.2.2"
pastey = "0.2.3"
substack = "1.1.1"
task-local = "0.1.1"
tokio = { version = "1.52.1", features = ["process"], optional = true }
tokio = { version = "1.52.3", features = ["process"], optional = true }
tokio-util = { version = "0.7.18", features = ["compat"], optional = true }
trait-set = "0.3.0"
unsync-pipe = { version = "0.2.0", path = "../unsync-pipe" }
[features]
tokio = ["dep:tokio", "dep:tokio-util", "dep:libloading"]
tokio = ["dep:tokio", "dep:tokio-util", "dep:libloading", "orchid-base/tokio"]
orchid-extension = ["dep:orchid-extension"]
+2 -2
View File
@@ -272,7 +272,7 @@ impl ox::System for CmdSystemInst {
ox::tree::fun(true, "sleep", async |until: ox::ForeignAtom, cont: ox::Expr| {
let Some(until) = until.call(ox::std_reqs::AsInstant).await else {
return ox::gen_expr::bot(mk_errv(
is("Not an instant").await,
is!("Not an instant"),
format!("{} is not an instant", fmt(&until).await),
[get_arg(0).pos().await],
));
@@ -289,7 +289,7 @@ impl ox::System for CmdSystemInst {
}),
])
}
async fn prelude(&self) -> Vec<Sym> { vec![sym!("orchid")] }
async fn prelude(&self) -> Vec<Sym> { vec![sym!(orchid)] }
fn lexers(&self) -> Vec<ox::LexerObj> { vec![] }
fn parsers(&self) -> Vec<ox::ParserObj> { vec![] }
async fn request(&self, _hand: Box<dyn ReqHandle>, req: ox::ReqForSystem<Self>) -> Receipt {
+8 -42
View File
@@ -4,9 +4,9 @@ use std::rc::{Rc, Weak};
use std::time::Duration;
use std::{fmt, ops};
use futures::future::LocalBoxFuture;
use futures_locks::RwLock;
use hashbrown::HashMap;
use orchid_base::{SpawnExt, SpawnSrv};
use crate::api;
use crate::expr_store::ExprStore;
@@ -14,30 +14,8 @@ use crate::logger::LoggerImpl;
use crate::system::{System, WeakSystem};
use crate::tree::WeakRoot;
pub trait JoinHandle {
/// It is guaranteed that the future will never be polled after this is called
fn abort(&self);
/// take the future out of the task. If the return value
/// is dropped, the spawned future is also dropped. The returned boxed future
/// will finish any sleep requested at spawn.
fn join(self: Box<Self>) -> LocalBoxFuture<'static, ()>;
}
pub trait Spawner {
/// Spawn a future off-task, but in the same thread. If all objects associated
/// with the owning [cx] are dropped (eg. expressions, extensions, systems)
/// and no extensions create permanent ref loops, then all tasks will
/// eventually settle, therefore the implementor of this interface should not
/// exit while there are pending tasks to allow external communication
/// channels to cleanly shut down.
fn spawn_obj(&self, delay: Duration, fut: LocalBoxFuture<'static, ()>) -> Box<dyn JoinHandle>;
fn sleep(&self, delay: Duration) -> LocalBoxFuture<'static, ()> {
self.spawn_obj(delay, Box::pin(async move {})).join()
}
}
pub struct CxData {
spawner: Rc<dyn Spawner>,
spawner: Rc<dyn SpawnSrv>,
pub systems: RwLock<HashMap<api::SysId, WeakSystem>>,
pub system_id: RefCell<NonZeroU16>,
pub exprs: ExprStore,
@@ -51,8 +29,8 @@ impl ops::Deref for Cx {
fn deref(&self) -> &Self::Target { &self.0 }
}
#[derive(Clone)]
pub struct Weakcx(Weak<CxData>);
impl Weakcx {
pub struct WeakCx(Weak<CxData>);
impl WeakCx {
#[must_use]
pub fn try_upgrade(&self) -> Option<Cx> { Some(Cx(self.0.upgrade()?)) }
#[must_use]
@@ -60,9 +38,9 @@ impl Weakcx {
}
impl Cx {
#[must_use]
pub fn new(spawner: impl Spawner + 'static, logger: LoggerImpl) -> Self {
pub fn new(spawner: Rc<dyn SpawnSrv>, logger: LoggerImpl) -> Self {
Self(Rc::new(CxData {
spawner: Rc::new(spawner),
spawner,
systems: RwLock::default(),
system_id: RefCell::new(NonZero::new(1).unwrap()),
exprs: ExprStore::default(),
@@ -70,19 +48,7 @@ impl Cx {
logger,
}))
}
/// Spawn a parallel future that you can join at any later time.
///
/// Don't use this for async Drop, use [orchid_base::stash] instead.
/// If you use this for an actor object, make sure to actually join the
/// handle.
#[must_use]
pub fn spawn(
&self,
delay: Duration,
fut: impl Future<Output = ()> + 'static,
) -> Box<dyn JoinHandle> {
self.spawner.spawn_obj(delay, Box::pin(fut))
}
pub fn spawn(&self) -> Rc<dyn SpawnSrv> { self.spawner.clone() }
/// Return after an amount of time has passed
#[must_use]
pub async fn sleep(&self, delay: Duration) { self.spawner.sleep(delay).await }
@@ -97,7 +63,7 @@ impl Cx {
api::SysId(*g)
}
#[must_use]
pub fn downgrade(&self) -> Weakcx { Weakcx(Rc::downgrade(&self.0)) }
pub fn downgrade(&self) -> WeakCx { WeakCx(Rc::downgrade(&self.0)) }
}
impl fmt::Debug for Cx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+5 -7
View File
@@ -16,16 +16,14 @@ impl AbsPathError {
pub async fn err_obj(self, pos: Pos, path: &str) -> OrcErrv {
let (descr, msg) = match self {
AbsPathError::RootPath => (
is("Path ends on root module").await,
is!("Path ends on root module"),
format!(
"{path} is equal to the empty path. You cannot directly reference the root. \
Use one fewer 'super::' or add more segments to make it valid."
),
),
AbsPathError::TooManySupers => (
is("Too many 'super::' steps in path").await,
format!("{path} is leading outside the root."),
),
AbsPathError::TooManySupers =>
(is!("Too many 'super::' steps in path"), format!("{path} is leading outside the root.")),
};
mk_errv(descr, msg, [pos])
}
@@ -39,8 +37,8 @@ impl AbsPathError {
/// if the relative path contains as many or more `super` segments than the
/// length of the absolute path.
pub async fn absolute_path(mut cwd: &[IStr], mut rel: &[IStr]) -> Result<VName, AbsPathError> {
let i_self = is("self").await;
let i_super = is("super").await;
let i_self = is!("self");
let i_super = is!("super");
let mut relative = false;
if let Some((_, tail)) = rel.split_first().filter(|(h, _)| **h == i_self) {
rel = tail;
+65 -10
View File
@@ -7,13 +7,13 @@ use futures::io::BufReader;
use futures::{AsyncBufReadExt, StreamExt};
use hashbrown::HashMap;
use libloading::{Library, Symbol};
use orchid_base::{log, on_drop, vt_to_future};
use orchid_base::{SpawnExt, future_to_vt, log, on_drop, vt_to_future};
use unsync_pipe::pipe;
use crate::api;
use crate::cx::Cx;
use crate::extension::ExtPort;
use crate::task_set::TaskSet;
use crate::task_set::{TaskSet, WeakTaskSet};
static DYNAMIC_LIBRARIES: Mutex<Option<HashMap<PathBuf, Arc<Library>>>> = Mutex::new(None);
fn load_dylib(path: &Path) -> Result<Arc<Library>, libloading::Error> {
@@ -33,7 +33,7 @@ pub async fn ext_dylib(path: &Path, cx: Cx) -> Result<ExtPort, libloading::Error
let (output, read_output) = pipe(1024);
let (write_log, read_log) = pipe(1024);
let log_path = path.to_string_lossy().to_string();
let _ = cx.spawn(Duration::ZERO, async move {
let _ = cx.spawn().spawn(async move {
let mut lines = BufReader::new(read_log).lines();
while let Some(line) = lines.next().await {
match line {
@@ -51,7 +51,7 @@ pub async fn ext_dylib(path: &Path, cx: Cx) -> Result<ExtPort, libloading::Error
unsafe { library.get("orchid_extension_main") }?;
let data = Box::into_raw(Box::new(SpawnerState { cx, tasks: tasks.clone() })) as *const ();
let spawner = api::binary::SpawnerBin { data, drop, spawn };
let spawner = api::binary::SpawnerBin { data, drop: drop_spawner, spawn, sleep };
let cx = api::binary::ExtensionContext { input, output, log: write_log, spawner };
unsafe { (entrypoint)(cx) };
Ok(ExtPort {
@@ -65,23 +65,78 @@ struct SpawnerState {
cx: Cx,
tasks: TaskSet,
}
extern "C" fn drop(data: *const ()) {
extern "C" fn drop_spawner(data: *const ()) {
let state = unsafe { Box::from_raw(data as *mut SpawnerState) };
state.tasks.abort_all();
}
extern "C" fn spawn(data: *const (), delay: u64, vt: api::binary::FutureBin) {
extern "C" fn sleep(data: *const (), millis: u64) -> api::binary::FutureBin {
// SAFETY: this is technically a Box but it can be used directly as a &mut
let state = unsafe { (data as *mut SpawnerState).as_mut() }.unwrap();
future_to_vt(state.cx.sleep(Duration::from_millis(millis)))
}
extern "C" fn spawn(
data: *const (),
keepalive: bool,
vt: api::binary::FutureBin,
) -> api::binary::TaskHandleBin {
// TODO: a new DynTaskHandle implementor that uses these IDs
let future = vt_to_future(vt);
// SAFETY: this is technically a Box but it can be used directly as a &mut
let state = unsafe { (data as *mut SpawnerState).as_mut() }.unwrap();
state.tasks.with(|store| {
let id = state.tasks.with(|store| {
store.add_with(|id| {
state.cx.spawn(Duration::from_millis(delay), async move {
let fut = async move {
future.await;
// SAFETY: We know this is live because the drop handle that frees it also
// aborts the future
let state = unsafe { (data as *mut SpawnerState).as_mut() }.unwrap();
state.tasks.with(|store| store.remove(id));
})
});
};
let spawner = state.cx.spawn();
let handle = if keepalive { spawner.spawn(fut) } else { spawner.spawn_weak(fut) };
(handle, id as u64)
})
});
let handle_data = if let Some(id) = id {
let handle = TaskHandle { id, tasks: state.tasks.downgrade() };
Box::into_raw(Box::new(handle)) as *const ()
} else {
std::ptr::null()
};
api::binary::TaskHandleBin { data: handle_data, drop: drop_task, abort, join }
}
struct TaskHandle {
id: u64,
tasks: WeakTaskSet,
}
extern "C" fn drop_task(data: *const ()) {
if !data.is_null() {
// SAFETY: this type is constructed in spawn, and the pointer is not null
unsafe { std::mem::drop(Box::<TaskHandle>::from_raw(data as *mut _)) };
}
}
extern "C" fn abort(data: *const ()) {
if data.is_null() {
return;
}
// SAFETY: this type is constructed in spawn, and the pointer is not null
let handle = unsafe { Box::<TaskHandle>::from_raw(data as *mut _) };
let Some(tasks) = handle.tasks.upgrade() else { return };
tasks.with(|store| store.remove(handle.id as usize).abort());
}
extern "C" fn join(data: *const ()) -> api::binary::FutureBin {
if data.is_null() {
return future_to_vt(futures::future::pending());
}
// SAFETY: this type is constructed in spawn, and the pointer is not null
let handle = unsafe { Box::<TaskHandle>::from_raw(data as *mut _) };
let Some(tasks) = handle.tasks.upgrade() else { return future_to_vt(futures::future::pending()) };
let Some(task) = tasks.with(|store| store.remove(handle.id as usize)) else {
return future_to_vt(futures::future::pending());
};
future_to_vt(task.join())
}
+2 -1
View File
@@ -76,7 +76,8 @@ impl Expr {
Expr::from_api(*x, rpsb, cx).boxed_local().await,
)
},
api::ExpressionKind::Const(name) => ExprKind::Const(Sym::from_api(name).await),
api::ExpressionKind::Const(name) =>
ExprKind::Const(Sym::from_api(name).await.expect("Const name freed before const received")),
api::ExpressionKind::Lambda(x, body) => {
let lbuilder = psb.lambda(&x);
let body = Expr::from_api(*body, lbuilder.stack(), cx).boxed_local().await;
+54 -30
View File
@@ -5,7 +5,6 @@ use std::io;
use std::num::NonZeroU64;
use std::pin::Pin;
use std::rc::{Rc, Weak};
use std::time::Duration;
use derive_destructure::destructure;
use futures::channel::mpsc::{Sender, channel};
@@ -17,12 +16,13 @@ use itertools::Itertools;
use orchid_api_traits::{Decode, Encode, Request};
use orchid_base::{
AtomRepr, Client, ClientExt, CommCx, FmtCxImpl, Format, IStr, IStrv, IoComm, MsgReaderExt, Pos,
ReqHandleExt, ReqReaderExt, Sym, Witness, es, ev, io_comm, is, iv, log, stash, with_stash,
ReqHandleExt, ReqReaderExt, SpawnExt, Sym, TaskHandle, Witness, es, ev, io_comm, is, iv, log,
stash, with_stash,
};
use crate::api;
use crate::atom::AtomHand;
use crate::cx::{Cx, JoinHandle};
use crate::cx::Cx;
use crate::dealias::{ChildError, ChildErrorKind, walk};
use crate::expr::{Expr, ExprFromApiCx, PathSetBuilder};
use crate::system::SystemCtor;
@@ -51,7 +51,7 @@ pub struct ExtensionData {
name: String,
cx: Cx,
comm_cx: Option<CommCx>,
join_ext: Option<Box<dyn JoinHandle>>,
join_ext: Option<TaskHandle<()>>,
client: Rc<dyn Client>,
systems: Vec<SystemCtor>,
next_pars: RefCell<NonZeroU64>,
@@ -90,7 +90,7 @@ impl Extension {
let weak2 = weak;
let weak = weak.clone();
let cx2 = cx.clone();
let join_ext = cx.clone().spawn(Duration::ZERO, async move {
let join_ext = cx.clone().spawn().spawn(async move {
srv
.listen(
async |reader| {
@@ -110,12 +110,22 @@ impl Extension {
this.0.cx.exprs.take_expr(rel.1);
},
api::ExtHostNotif::Log(api::Log { category, message }) =>
write!(log(&es(category).await), "{message}").await,
write!(log(&es(category).await.expect("Log category stale")), "{message}").await,
api::ExtHostNotif::Sweeped(data) => {
for i in join_all(data.strings.into_iter().map(es)).await {
for i in
join_all(data.strings.into_iter().map(async |s| {
es(s).await.expect("Sweeped token must have existed before")
}))
.await
{
this.0.itable.borrow_mut().strings.remove(&i);
}
for i in join_all(data.vecs.into_iter().map(ev)).await {
for i in
join_all(data.vecs.into_iter().map(async |s| {
ev(s).await.expect("Sweeped token must have existed before")
}))
.await
{
this.0.itable.borrow_mut().string_vecs.remove(&i);
}
},
@@ -145,23 +155,32 @@ impl Extension {
handle.reply(&s, i.to_api()).await
},
api::IntReq::InternStrv(v) => {
let tokens = join_all(v.0.iter().map(|m| es(*m))).await;
let tokens = join_all(
v.0
.iter()
.map(|m| async { es(*m).await.expect("Intern strv of stale strings") }),
)
.await;
this.0.itable.borrow_mut().strings.extend(tokens.iter().cloned());
let i = iv(&tokens).await;
this.0.itable.borrow_mut().string_vecs.insert(i.clone());
handle.reply(&v, i.to_api()).await
},
api::IntReq::ExternStr(si) => {
let i = es(si.0).await;
this.0.itable.borrow_mut().strings.insert(i.clone());
handle.reply(&si, i.to_string()).await
api::IntReq::ExternStr(si) => match es(si.0).await {
Err(e) => handle.reply(&si, Err(e)).await,
Ok(i) => {
this.0.itable.borrow_mut().strings.insert(i.clone());
handle.reply(&si, Ok(i.to_string())).await
},
},
api::IntReq::ExternStrv(vi) => {
let i = ev(vi.0).await;
this.0.itable.borrow_mut().strings.extend(i.iter().cloned());
this.0.itable.borrow_mut().string_vecs.insert(i.clone());
let markerv = i.iter().map(|t| t.to_api()).collect_vec();
handle.reply(&vi, markerv).await
api::IntReq::ExternStrv(vi) => match ev(vi.0).await {
Err(e) => handle.reply(&vi, Err(e)).await,
Ok(i) => {
this.0.itable.borrow_mut().strings.extend(i.iter().cloned());
this.0.itable.borrow_mut().string_vecs.insert(i.clone());
let markerv = i.iter().map(|t| t.to_api()).collect_vec();
handle.reply(&vi, Ok(markerv)).await
},
},
},
api::ExtHostReq::Fwd(ref fw @ api::Fwd { ref target, ref method, ref body }) => {
@@ -223,7 +242,7 @@ impl Extension {
api::ExtHostReq::LsModule(ref ls @ api::LsModule(_sys, path)) => {
let reply: <api::LsModule as Request>::Response =
'reply: {
let path = ev(path).await;
let path = ev(path).await.expect("LsModule issued for stale path");
let root = (cx.root.read().await.upgrade())
.expect("LSModule called when root isn't in context");
let root_data = &*root.0.read().await;
@@ -261,8 +280,12 @@ impl Extension {
let result = root
.resolve_name(
cx.clone(),
Sym::from_api(*path).await,
Sym::from_api(*name).await,
Sym::from_api(*path)
.await
.expect("Extension dropped path from ResolveName query"),
Sym::from_api(*name)
.await
.expect("Extension dropped name from ResolveName query"),
)
.await;
let rep = match result {
@@ -271,7 +294,11 @@ impl Extension {
.extend(e.iter().map(|e| e.description.clone()));
Err(e.to_api())
},
Ok(name) => Ok(name.to_sym().await.to_api()),
Ok(name) => {
let sym = name.to_sym().await;
this.0.itable.borrow_mut().string_vecs.insert(sym.tok());
Ok(sym.to_api())
},
};
handle.reply(rn, rep).await
},
@@ -351,13 +378,10 @@ impl Extension {
}
pub fn system_drop(&self, id: api::SysId) {
let rc = self.clone();
let _ = self.cx().spawn(
Duration::ZERO,
with_stash(async move {
rc.client().request(api::SystemDrop(id)).await.unwrap();
rc.cx().systems.write().await.remove(&id);
}),
);
let _ = self.cx().spawn().spawn(with_stash(async move {
rc.client().request(api::SystemDrop(id)).await.unwrap();
rc.cx().systems.write().await.remove(&id);
}));
}
pub fn itable(&self) -> &RefCell<InternedTable> { &self.0.itable }
#[must_use]
+4 -17
View File
@@ -1,10 +1,8 @@
use std::io;
use std::rc::Rc;
use std::time::Duration;
use futures::io::BufReader;
use futures::{AsyncBufReadExt, StreamExt};
use orchid_base::{log, on_drop};
use orchid_base::{SpawnExt, log, on_drop};
use orchid_extension as ox;
use unsync_pipe::pipe;
@@ -19,7 +17,7 @@ pub async fn ext_inline(builder: ox::ExtensionBuilder, cx: Cx) -> ExtPort {
let name = builder.name;
std::mem::drop(cx.spawn(Duration::ZERO, async move {
std::mem::drop(cx.spawn().spawn(async move {
let mut lines = BufReader::new(out_stderr).lines();
while let Some(line) = lines.next().await {
match line {
@@ -35,25 +33,14 @@ pub async fn ext_inline(builder: ox::ExtensionBuilder, cx: Cx) -> ExtPort {
let task_set = TaskSet::default();
let task_set1 = task_set.clone();
std::mem::drop(cx.clone().spawn(Duration::ZERO, async move {
std::mem::drop(cx.spawn().spawn(async move {
let task_set2 = task_set1.clone();
builder
.run(ox::ExtPort {
input: Box::pin(out_stdin),
output: Box::pin(in_stdout),
log: Box::pin(in_stderr),
spawn: Rc::new(move |delay, fut| {
let cx1 = cx.clone();
let task_set2 = task_set1.clone();
task_set1.with(move |store| {
store.add_with(move |id| {
cx1.spawn(delay, async move {
fut.await;
task_set2.with(|store| store.remove(id));
})
})
});
}),
spawn: cx.spawn(),
})
.await;
task_set2.abort_all();
+6 -8
View File
@@ -121,7 +121,7 @@ pub async fn lex_once(cx: &mut LexCx<'_>, exactly_one: bool) -> OrcRes<bool> {
let mut sub_cx = cx.sub(cx.pos_from(tail), &mut produced);
if !lex_once(&mut sub_cx, exactly_one).boxed_local().await? {
return Err(mk_errv(
is("Unexpected end of source text").await,
is!("Unexpected end of source text"),
":: cannot be the last token",
[SrcRange::new(start..cx.get_pos(), &cx.pcx.src())],
));
@@ -138,7 +138,7 @@ pub async fn lex_once(cx: &mut LexCx<'_>, exactly_one: bool) -> OrcRes<bool> {
} else if cx.strip_prefix("--[") {
let Some((cmt, tail)) = cx.tail.split_once("]--") else {
return Err(mk_errv(
is("Unterminated block comment").await,
is!("Unterminated block comment"),
"This block comment has no ending ]--",
[SrcRange::new(start..start + 3, &cx.pcx.src())],
));
@@ -155,7 +155,7 @@ pub async fn lex_once(cx: &mut LexCx<'_>, exactly_one: bool) -> OrcRes<bool> {
let mut sub_cx = cx.sub(cx.pos_from(tail), &mut produced);
if !lex_once(&mut sub_cx, exactly_one).boxed_local().await? {
return Err(mk_errv(
is("Unexpected end of file").await,
is!("Unexpected end of file"),
"Expected a lambda argument and body",
[SrcRange::new(start..cx.get_pos(), &cx.pcx.src())],
));
@@ -176,7 +176,7 @@ pub async fn lex_once(cx: &mut LexCx<'_>, exactly_one: bool) -> OrcRes<bool> {
let mut sub_cx = cx.sub(cx.get_pos(), &mut body);
if !lex_once(&mut sub_cx, false).boxed_local().await? {
return Err(mk_errv(
is("unclosed paren").await,
is!("unclosed paren"),
format!("this {lp} has no matching {rp}"),
[SrcRange::new(start..start + 1, &cx.pcx.src())],
));
@@ -223,7 +223,7 @@ pub async fn lex_once(cx: &mut LexCx<'_>, exactly_one: bool) -> OrcRes<bool> {
return Ok(true);
} else {
return Err(mk_errv(
is("Unrecognized character").await,
is!("Unrecognized character"),
"The following syntax is meaningless.",
[SrcRange::new(start..start + 1, &cx.pcx.src())],
));
@@ -251,11 +251,10 @@ pub async fn sys_lex(cx: &mut LexCx<'_>) -> Option<OrcRes<Vec<ParsTokTree>>> {
let mut sub_cx = cx_g.sub(pos, &mut produced);
let lex_res = lex_once(&mut sub_cx, true).boxed_local().await;
let pos1 = sub_cx.get_pos();
eprintln!("Setting sublex pos {pos1}");
cx_g.set_pos(pos1);
match lex_res {
Ok(false) => {
let errv = mk_errv(is("End of file").await, "Unexpected end of source text", [
let errv = mk_errv(is!("End of file"), "Unexpected end of source text", [
cx_g.sr_to(pos),
]);
Err(errv.to_api())
@@ -282,7 +281,6 @@ pub async fn sys_lex(cx: &mut LexCx<'_>) -> Option<OrcRes<Vec<ParsTokTree>>> {
return Some(Err(OrcErrv::from_api(e).await));
},
Ok(Some(lexed)) => {
eprintln!("Lexer plugin ended at {}", lexed.pos);
cx.set_pos(lexed.pos);
let mut stable_trees = Vec::new();
for tok in lexed.expr {
+12 -10
View File
@@ -99,16 +99,16 @@ pub async fn parse_item(
) -> OrcRes<Vec<Item>> {
match item.split_first() {
Some((TokTree { tok: Token::Name(n), .. }, postdisc)) => match n {
n if *n == is("export").await => match try_pop_no_fluff(postdisc).await? {
n if *n == is!("export") => match try_pop_no_fluff(postdisc).await? {
Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } =>
parse_exportable_item(cx, path, comments, true, n.clone(), tail).await,
Parsed { output, tail: _ } => Err(mk_errv(
is("Malformed export").await,
is!("Malformed export"),
"`export` can either prefix other lines or list names inside ( )",
[output.sr()],
)),
},
n if *n == is("import").await => {
n if *n == is!("import") => {
let imports = parse_import(postdisc).await?;
Ok(Vec::from_iter(imports.into_iter().map(|t| Item {
comments: comments.clone(),
@@ -119,7 +119,7 @@ pub async fn parse_item(
n => parse_exportable_item(cx, path, comments, false, n.clone(), postdisc).await,
},
Some(_) =>
Err(mk_errv(is("Expected a line type").await, "All lines must begin with a keyword", [
Err(mk_errv(is!("Expected a line type"), "All lines must begin with a keyword", [
item.sr()
])),
None => unreachable!("These lines are filtered and aggregated in earlier stages"),
@@ -140,7 +140,7 @@ pub async fn parse_exportable_item<'a>(
discr: IStr,
tail: ParsSnippet<'a>,
) -> OrcRes<Vec<Item>> {
let kind = if discr == is("mod").await {
let kind = if discr == is!("mod") {
let (name, body) = parse_module(cx, path, tail).await?;
ItemKind::Member(ParsedMember { name, exported, kind: ParsedMemKind::Mod(body) })
} else if let Some(parser) = cx.systems().iter().find_map(|s| s.get_parser(discr.clone())) {
@@ -153,7 +153,7 @@ pub async fn parse_exportable_item<'a>(
} else {
let ext_lines = cx.systems().iter().flat_map(System::line_types).join(", ");
return Err(mk_errv(
is("Unrecognized line type").await,
is!("Unrecognized line type"),
format!("Line types are: mod, {ext_lines}"),
[tail.prev().sr()],
));
@@ -170,7 +170,7 @@ pub async fn parse_module<'a>(
Parsed { output: TokTree { tok: Token::Name(n), .. }, tail } => (n.clone(), tail),
Parsed { output, .. } => {
return Err(mk_errv(
is("Missing module name").await,
is!("Missing module name"),
format!("A name was expected, {} was found", fmt(output).await),
[output.sr()],
));
@@ -180,7 +180,7 @@ pub async fn parse_module<'a>(
expect_end(surplus).await?;
let Some(body) = output.as_s(Paren::Round) else {
return Err(mk_errv(
is("Expected module body").await,
is!("Expected module body"),
format!("A ( block ) was expected, {} was found", fmt(output).await),
[output.sr()],
));
@@ -259,8 +259,10 @@ pub async fn lower(
ParsTok::Bottom(err) => ExprKind::Bottom(err.clone()).at(tt.sr.pos()),
ParsTok::Handle(expr) | ParsTok::NewExpr(expr) => expr.clone(),
ParsTok::Reflexive(flex) => {
let flex_tt = flex.get(&mut *cx.flex_repo().lock().await, &path, &cx.src()).await;
lower(&flex_tt, builder, path, cx).boxed_local().await
match flex.get(&mut *cx.flex_repo().lock().await, &path, &cx.src()).await {
Ok(flex_tt) => lower(&flex_tt, builder, path, cx).boxed_local().await,
Err(e) => ExprKind::Bottom(e).at(tt.sr.pos()),
}
},
ParsTok::NS(..) | ParsTok::Name(_) => {
let Ok(local_name) = tt.as_multiname() else {
+24 -17
View File
@@ -7,8 +7,9 @@ use futures::future::{LocalBoxFuture, join_all};
use hashbrown::{HashMap, HashSet};
use itertools::Itertools;
use orchid_base::{
ClientExt, Comment, FmtCx, FmtUnit, Format, IStr, IStrv, Import, ManagedStrat, Snippet, SrcRange,
Sym, TokFromApiCx, TokToApiCx, TokTree, Token, TokenVariant, Variants, stash, tl_cache, ttv_fmt,
ClientExt, Comment, FmtCx, FmtUnit, Format, IStr, IStrv, Import, ManagedStrat, OrcErrv, OrcRes,
Snippet, SrcRange, Sym, TokFromApiCx, TokToApiCx, TokTree, Token, TokenVariant, Variants, stash,
tl_cache, ttv_fmt,
};
use crate::api;
@@ -26,7 +27,7 @@ impl FlexTreeHandle {
pub fn to_api(&self) -> api::ReflexiveTree {
api::ReflexiveTree { sys: self.sys.id(), id: self.id }
}
pub async fn read(&self, path: &Sym) -> api::TokenTree {
pub async fn read(&self, path: &Sym) -> api::OrcResult<api::TokenTree> {
(self.sys.client())
.request(api::ReflexiveRead { path: path.to_api(), tree: self.to_api() })
.await
@@ -43,22 +44,28 @@ impl Drop for FlexTreeHandle {
}
}
#[derive(Clone)]
pub struct FlexTree(Rc<FlexTreeHandle>);
pub struct FlexTree {
hand: Rc<FlexTreeHandle>,
}
impl FlexTree {
pub async fn get(&self, flex: &mut FlexTreeRepo, path: &Sym, src: &Sym) -> ParsTokTree {
let tt = self.0.read(path).await;
ParsTokTree::from_api(tt, &mut TokFromApiCx {
extra: &mut &self.0.sys.clone(),
handle: &mut ExprStore::default(),
src: src.clone(),
lazy: &mut (self.0.sys.clone(), flex),
})
.await
pub async fn get(&self, flex: &mut FlexTreeRepo, path: &Sym, src: &Sym) -> OrcRes<ParsTokTree> {
match self.hand.read(path).await {
Err(e) => Err(OrcErrv::from_api(e).await),
Ok(tt) => Ok(
ParsTokTree::from_api(tt, &mut TokFromApiCx {
extra: &mut &self.hand.sys.clone(),
handle: &mut ExprStore::default(),
src: src.clone(),
lazy: &mut (self.hand.sys.clone(), flex),
})
.await,
),
}
}
}
impl Debug for FlexTree {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}/flextree/{:?}", self.0.id, &self.0.sys)
write!(f, "{:?}/flextree/{:?}", self.hand.id, &self.hand.sys)
}
}
impl Format for FlexTree {
@@ -79,13 +86,13 @@ impl TokenVariant<api::ReflexiveTree> for FlexTree {
tree.clone()
} else {
assert_eq!(sys.id(), api.sys, "Newly constructed flextree for wrong system");
FlexTree(Rc::new(FlexTreeHandle { sys: sys.clone(), id: api.id }))
FlexTree { hand: Rc::new(FlexTreeHandle { sys: sys.clone(), id: api.id }) }
}
}
type ToApiCx<'a> = FlexTreeRepo;
async fn into_api(self, ctx: &mut Self::ToApiCx<'_>) -> api::ReflexiveTree {
ctx.0.entry(self.0.to_api()).or_insert_with(|| self.clone());
self.0.to_api()
ctx.0.entry(self.hand.to_api()).or_insert_with(|| self.clone());
self.hand.to_api()
}
}
+2 -4
View File
@@ -1,8 +1,6 @@
use std::time::Duration;
use futures::io::BufReader;
use futures::{self, AsyncBufReadExt, StreamExt};
use orchid_base::log;
use orchid_base::{SpawnExt, log};
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
use crate::cx::Cx;
@@ -19,7 +17,7 @@ pub async fn ext_command(cmd: std::process::Command, cx: Cx) -> std::io::Result<
let stdin = child.stdin.take().unwrap();
let stdout = child.stdout.take().unwrap();
let child_stderr = child.stderr.take().unwrap();
std::mem::drop(cx.spawn(Duration::ZERO, async move {
std::mem::drop(cx.spawn().spawn(async move {
let _ = child;
let mut lines = BufReader::new(child_stderr.compat()).lines();
while let Some(line) = lines.next().await {
+1 -1
View File
@@ -103,7 +103,7 @@ async fn conv(
continue;
},
};
let name = es(name).await;
let name = es(name).await.expect("Member name stale before parsed tree fully received");
let mem_path = module.push(name.clone());
let mkind = match kind {
api::ParsedMemberKind::Module { lines, use_prelude } => {
+15 -5
View File
@@ -2,14 +2,15 @@ use std::collections::VecDeque;
use std::fmt;
use std::future::Future;
use std::rc::{Rc, Weak};
use std::time::Duration;
use derive_destructure::destructure;
use futures::future::join_all;
use futures_locks::RwLock;
use hashbrown::HashMap;
use itertools::Itertools;
use orchid_base::{Client, ClientExt, FmtCx, FmtUnit, Format, IStr, Sym, char_filter_match, es};
use orchid_base::{
Client, ClientExt, FmtCx, FmtUnit, Format, IStr, SpawnExt, Sym, char_filter_match, es,
};
use ordered_float::NotNan;
use substack::{Stackframe, Substack};
@@ -114,7 +115,7 @@ impl System {
}
pub(crate) fn drop_atom(&self, dropped_atom_id: api::AtomId) {
let this = self.0.clone();
let _ = self.0.cx.spawn(Duration::ZERO, async move {
let _ = self.0.cx.spawn().spawn(async move {
this.ext.client().request(api::AtomDrop(this.id, dropped_atom_id)).await.unwrap();
this.owned_atoms.write().await.remove(&dropped_atom_id);
});
@@ -175,9 +176,18 @@ impl SystemCtor {
ext: ext.clone(),
cx: ext.cx().clone(),
lex_filter: sys_inst.lex_filter,
line_types: join_all(sys_inst.line_types.iter().map(|m| es(*m))).await,
line_types: join_all(
sys_inst
.line_types
.iter()
.map(async |m| es(*m).await.expect("line_types should be statically interned")),
)
.await,
id,
prelude: join_all(sys_inst.prelude.iter().map(|tok| Sym::from_api(*tok))).await,
prelude: join_all(sys_inst.prelude.iter().map(async |tok| {
Sym::from_api(*tok).await.expect("prelude items should be statically interned")
}))
.await,
owned_atoms: RwLock::new(HashMap::new()),
}));
let api_module_root = api::Module {
+11 -5
View File
@@ -1,16 +1,22 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::rc::{Rc, Weak};
use orchid_base::IdStore;
use orchid_base::{IdStore, TaskHandle};
use crate::cx::JoinHandle;
pub struct WeakTaskSet {
pub tasks: Weak<RefCell<Option<IdStore<TaskHandle<()>>>>>,
}
impl WeakTaskSet {
pub fn upgrade(&self) -> Option<TaskSet> { Some(TaskSet { tasks: self.tasks.upgrade()? }) }
}
#[derive(Clone)]
pub struct TaskSet {
pub tasks: Rc<RefCell<Option<IdStore<Box<dyn JoinHandle>>>>>,
pub tasks: Rc<RefCell<Option<IdStore<TaskHandle<()>>>>>,
}
impl TaskSet {
pub fn with<R>(&self, f: impl FnOnce(&mut IdStore<Box<dyn JoinHandle>>) -> R) -> Option<R> {
pub fn downgrade(&self) -> WeakTaskSet { WeakTaskSet { tasks: Rc::downgrade(&self.tasks) } }
pub fn with<R>(&self, f: impl FnOnce(&mut IdStore<TaskHandle<()>>) -> R) -> Option<R> {
self.tasks.borrow_mut().as_mut().map(f)
}
pub fn abort_all(&self) {
+7 -7
View File
@@ -105,7 +105,7 @@ impl Root {
}
match module {
Ok(_) => Err(mk_errv(
is("module used as constant").await,
is!("module used as constant"),
format!("{name} is a module, not a constant"),
[pos],
)),
@@ -113,7 +113,7 @@ impl Root {
ChildErrorKind::Private => panic!("public_only is false"),
ChildErrorKind::Constant => panic!("Tree refers to constant not in table"),
ChildErrorKind::Missing => Err(mk_errv(
is("Constant does not exist").await,
is!("Constant does not exist"),
format!("{name} does not refer to a constant"),
[pos],
)),
@@ -138,7 +138,7 @@ impl Root {
Some(Ok(dest)) => return Ok(dest.target.to_vname().suffix(tail.iter().cloned())),
Some(Err(dests)) => {
return Err(mk_errv_floating(
is("Ambiguous name").await,
is!("Ambiguous name"),
format!("{selector} could refer to {}", dests.iter().map(|ri| &ri.target).display("or")),
));
},
@@ -153,7 +153,7 @@ impl Root {
return Ok(VPath::new(cwd.iter().cloned()).name_with_suffix(selector.clone()));
}
Err(mk_errv_floating(
is("Invalid name").await,
is!("Invalid name"),
format!(
"{selector} doesn't refer to a module from {orig}, valid options are {}",
options.iter().join(", ")
@@ -203,7 +203,7 @@ impl Mod {
pub async fn from_api(api: api::Module, cx: &mut TreeFromApicx<'_>) -> Self {
let mut members = HashMap::new();
for mem in api.members {
let mem_name = es(mem.name).await;
let mem_name = es(mem.name).await.expect("env tree items should be statically interned");
let vname = VPath::new(cx.path.iter().cloned()).name_with_suffix(mem_name.clone());
let name = vname.to_sym().await;
let (lazy, kind) = match mem.kind {
@@ -284,7 +284,7 @@ impl Mod {
}
}
}
let conflicting_imports_msg = is("Conflicting imports").await;
let conflicting_imports_msg = is!("Conflicting imports");
for (key, values) in imports_by_name {
if values.len() == 1 {
let import = values.into_iter().next().unwrap();
@@ -323,7 +323,7 @@ impl Mod {
imports.insert(key, if values.len() == 1 { Ok(values[0].clone()) } else { Err(values) });
}
}
let self_referential_msg = is("Self-referential import").await;
let self_referential_msg = is!("Self-referential import");
for (key, value) in imports.iter() {
let Ok(import) = value else { continue };
if import.target.strip_prefix(&path[..]).is_some_and(|t| t.starts_with(slice::from_ref(key)))
+7 -7
View File
@@ -16,10 +16,10 @@ path = "src/lib.rs"
async-event = "0.2.1"
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
async-once-cell = "0.5.4"
chrono = "0.4.44"
chrono = "0.4.45"
futures = { version = "0.3.32", features = ["std"], default-features = false }
hashbrown = "0.17.0"
itertools = "0.14.0"
hashbrown = "0.17.1"
itertools = "0.15.0"
never = "0.1.0"
once_cell = "1.21.4"
orchid-api = { version = "0.1.0", path = "../orchid-api" }
@@ -30,12 +30,12 @@ orchid-extension = { version = "0.1.0", path = "../orchid-extension", features =
"tokio",
] }
ordered-float = "5.3.0"
pastey = "0.2.2"
rust_decimal = "1.41.0"
pastey = "0.2.3"
rust_decimal = "1.42.1"
subslice-offset = "0.1.1"
substack = "1.1.1"
tokio = { version = "1.52.1", features = ["full"] }
unicode-segmentation = "1.13.2"
tokio = { version = "1.52.3", features = ["full"] }
unicode-segmentation = "1.13.3"
[dev-dependencies]
test_executors = "0.4.1"
+1 -1
View File
@@ -28,7 +28,7 @@ impl Parser for LetLine {
});
return Err(err.await);
};
let Parsed { tail, .. } = expect_tok(tail, is("=").await).await?;
let Parsed { tail, .. } = expect_tok(tail, is!("=")).await?;
// Parse a mactree that cannot contain placeholders and push all reflexive
// trees onto a vector to be returned separately and applied during execution
pub struct LetParsStrat(Vec<(api::ReflexiveTree, SrcRange)>);
+6 -6
View File
@@ -16,22 +16,22 @@ pub async fn lower(mt: &MacTree, args: Substack<'_, Sym>) -> GExpr {
MacTok::Resolved(inner) => lower(inner, args).boxed_local().await,
MacTok::Bottom(b) => bot(b.clone()),
MacTok::Slot(_) => bot(mk_errv(
is("Lowering intermediary slotted mactree").await,
is!("Lowering intermediary slotted mactree"),
"Found a Slot during lowering, which should only exist in temporary templates",
[mt.pos()],
)),
MacTok::Ph(ph) => bot(mk_errv(
is("Lowering placeholder").await,
is!("Lowering placeholder"),
format!("Found {ph} during lowering. Placeholders should only exist in macro patterns"),
[mt.pos()],
)),
MacTok::S(Paren::Curly | Paren::Square, _) => bot(mk_errv(
is("Lowering {...} or [...]").await,
is!("Lowering {...} or [...]"),
format!("Cannot lower this syntax, probable incorrect macro call: {}", fmt(mt).await),
[mt.pos()],
)),
MacTok::S(Paren::Round, b) if b.items.is_empty() => bot(mk_errv(
is("Cannot lower empty ()").await,
is!("Cannot lower empty ()"),
"Attempted to lower empty (). All expressions must have a value.",
[mt.pos()],
)),
@@ -39,7 +39,7 @@ pub async fn lower(mt: &MacTree, args: Substack<'_, Sym>) -> GExpr {
let MacTok::Name(n) = arg.tok() else {
return {
bot(mk_errv(
is("Lowering lambda with complex param").await,
is!("Lowering lambda with complex param"),
format!(
"In code after macros, lambda params can only be names, not {}",
fmt(arg).await
@@ -51,7 +51,7 @@ pub async fn lower(mt: &MacTree, args: Substack<'_, Sym>) -> GExpr {
if body.items.is_empty() {
return {
bot(mk_errv(
is("Lowering lambda with empty body").await,
is!("Lowering lambda with empty body"),
"Lambdas without a body are invalid, all expressions in Orchid must have a value",
[mt.pos()],
))
+8 -8
View File
@@ -33,7 +33,7 @@ impl Parser for MacroLine {
) -> OrcRes<Vec<ParsedLine>> {
if exported {
return Err(mk_errv(
is("macros are always exported").await,
is!("macros are always exported"),
"The export keyword is forbidden here to avoid confusion\n\
because macros are exported by default",
[line.sr()],
@@ -74,7 +74,7 @@ impl Parser for MacroLine {
let lines = line_items(Snippet::new(prio_or_body, body)).await;
let Some((kw_line, rule_lines)) = lines.split_first() else { return Ok(Vec::new()) };
let mut keywords = Vec::new();
let Parsed { tail: kw_tail, .. } = expect_tok(kw_line.tail, is("keywords").await).await?;
let Parsed { tail: kw_tail, .. } = expect_tok(kw_line.tail, is!("keywords")).await?;
for kw_tok in kw_tail.iter().filter(|kw| !kw.is_fluff()) {
match kw_tok.as_name() {
Some(kw) => {
@@ -90,7 +90,7 @@ impl Parser for MacroLine {
}
let Some((macro_name, _)) = keywords.first().cloned() else {
return Err(mk_errv(
is("macro with no keywords").await,
is!("macro with no keywords"),
"Macros must define at least one macro of their own.",
[kw_line.tail.sr()],
));
@@ -100,11 +100,11 @@ impl Parser for MacroLine {
for (idx, line) in rule_lines.iter().enumerate().map(|(n, v)| (n as u32, v)) {
let sr = line.tail.sr();
let name = is(&format!("rule::{}::{}", macro_name, idx)).await;
let Parsed { tail, .. } = expect_tok(line.tail, is("rule").await).await?;
let arrow_token = is("=>").await;
let Parsed { tail, .. } = expect_tok(line.tail, is!("rule")).await?;
let arrow_token = is!("=>");
let Some((pattern, body)) = tail.split_once(|tok| tok.is_kw(arrow_token.clone())) else {
report(mk_errv(
is("Missing => in rule").await,
is!("Missing => in rule"),
"Rule lines are of the form `rule ...pattern => ...body`",
[line.tail.sr()],
));
@@ -119,7 +119,7 @@ impl Parser for MacroLine {
sr: SrcRange,
) -> Result<crate::MacTree, Self::Err> {
Err(mk_errv(
is("Unsupported token in macro pattern").await,
is!("Unsupported token in macro pattern"),
"Patterns don't support atoms and other complex token types",
[sr],
))
@@ -162,7 +162,7 @@ impl Parser for MacroLine {
comments: line.output.clone(),
sr: sr.clone(),
kind: ParsedLineKind::Rec(
[Token::Name(is("let").await), Token::Name(name.clone()), Token::Name(is("=").await)]
[Token::Name(is!("let")), Token::Name(name.clone()), Token::Name(is!("="))]
.into_iter()
.map(|t| t.at(sr.clone()))
.chain(body_tt)
+1
View File
@@ -65,6 +65,7 @@ impl System for MacroSystemInst {
sym!(pattern::ref),
sym!(pattern::=>),
sym!(std::fn::[|>]),
sym!(std::fn::[$]),
]
}
fn lexers(&self) -> Vec<LexerObj> { vec![&MacTreeLexer, &PhLexer] }
+1 -1
View File
@@ -239,7 +239,7 @@ impl MacTok {
impl Format for MacTok {
async fn print<'a>(&'a self, c: &'a (impl FmtCx + ?Sized + 'a)) -> FmtUnit {
match self {
Self::Value(v) => tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("$({0b})")))
Self::Value(v) => tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("{0b}")))
.units([v.print(c).await]),
Self::Lambda(arg, b) => tl_cache!(Rc<Variants>: Rc::new(Variants::default()
// .unbounded("\\{0} {1l}")
+3 -4
View File
@@ -1,7 +1,7 @@
use std::borrow::Cow;
use std::ops::RangeInclusive;
use itertools::{Itertools};
use itertools::Itertools;
use never::Never;
use orchid_base::{OrcRes, Paren, clone, token_errv};
use orchid_extension::gen_expr::new_atom;
@@ -20,7 +20,6 @@ pub struct MacTreeLexer;
impl Lexer for MacTreeLexer {
const CHAR_FILTER: &'static [RangeInclusive<char>] = &['\''..='\''];
async fn lex<'a>(tail: &'a str, lctx: &'a LexContext<'a>) -> OrcRes<(&'a str, GenTokTree)> {
eprintln!("Lexing toktree");
let Some(tail2) = tail.strip_prefix('\'') else {
return Err(err_not_applicable().await);
};
@@ -53,8 +52,8 @@ impl Lexer for MacTreeLexer {
flex: api::ReflexiveTree,
sr: orchid_base::SrcRange,
) -> Result<MacTree, Self::Err> {
self.0.push(Cow::Owned(GenTok::Reflexive(FlexTree::Existing(flex)).at(sr)));
todo!("Review the todo in macro_line and replicate it here")
self.0.push(Cow::Owned(GenTok::Reflexive(FlexTree::Existing(flex)).at(sr.clone())));
Ok(MacTok::Slot(SlotFillStrategy::Value).at(sr))
}
}
let mut strat = Strat(Vec::new());
+22 -7
View File
@@ -1,9 +1,11 @@
use std::borrow::Cow;
use std::rc::Rc;
use futures::future::join_all;
use futures::{StreamExt, stream};
use itertools::Itertools;
use never::Never;
use orchid_base::{OrcRes, Sym, fmt, is, mk_errv};
use orchid_base::{OrcRes, Sym, Variants, fmt, is, mk_errv, tl_cache};
use orchid_extension::gen_expr::{bot, call, call_v, lam, new_atom};
use orchid_extension::tree::{GenMember, fun, prefix};
use orchid_extension::{Atomic, ExecHandle, Expr, OwnedAtom, OwnedVariant, TAtom, ToExpr, exec};
@@ -27,6 +29,13 @@ impl Atomic for MatcherAtom {
impl OwnedAtom for MatcherAtom {
type Refs = Never;
async fn val(&self) -> std::borrow::Cow<'_, Self::Data> { Cow::Owned(()) }
async fn print_atom<'a>(
&'a self,
c: &'a (impl orchid_base::FmtCx + ?Sized + 'a),
) -> orchid_base::FmtUnit {
tl_cache!(Rc<Variants>: Rc::new(Variants::default().bounded("Matcher[{0b}]({1b})")))
.units([self.keys.iter().join(", ").into(), self.matcher.print_atom(c).await])
}
}
pub async fn match_one(
@@ -48,7 +57,10 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
}),
fun(true, "matcher", async |names: HomoTpl<TAtom<SymAtom>>, matcher: TAtom<MacTree>| {
new_atom(MatcherAtom {
keys: join_all(names.0.iter().map(async |atm| Sym::from_api(atm.0).await)).await,
keys: join_all(names.0.iter().map(async |atm| {
Sym::from_api(atm.0).await.expect("SymAtom should keep the sym from going stale")
}))
.await,
matcher: matcher.own().await,
})
}),
@@ -61,7 +73,7 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
},
)
.finish(),
build_macro(None, ["match", "match_rule", "_row", "=>"])
build_macro(None, ["match", "match_rule", "_row"])
.rule(mactreev!("pattern::match" { "..$" rules 0 }), async |mut cx, [rules]| {
let desugared = mactree!("l" "pattern::implicit_value" (
"pattern::match" "pattern::implicit_value" {
@@ -81,9 +93,10 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
cx.recur_call(mactree!("pattern::_row" "push" line_mac.own().await)).await?;
rule_atoms.push((matcher, body));
}
eprintln!("Before base case created");
let base_case = lam(async |_| {
bot(mk_errv(
is("No branches match").await,
is!("No branches match"),
"None of the patterns matches this value",
[rules.pos()],
))
@@ -91,11 +104,12 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
.await
.create()
.await;
eprintln!("after base case created");
let match_chain = stream::iter(rule_atoms.into_iter().rev())
.fold(mactree!("Val" base_case), async |tail, (mat, body)| {
mactree!("l" "pattern::x" (
"pattern::match_one"
"Val" mat.ex();
("push" mat.own().await.matcher)
"pattern::x"
"push" body.own().await;
("push" tail;
@@ -103,6 +117,7 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
))
})
.await;
eprintln!("after match chain created");
Ok(mactree!("push" match_chain; "push" value))
},
)
@@ -123,7 +138,7 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
cx.recur_call(mactree!("pattern::match_rule" "push" pattern.clone())).await
else {
return Err(mk_errv(
is("Invalid pattern").await,
is!("Invalid pattern"),
format!("Could not parse {} as a match pattern", fmt(&pattern).await),
[pattern.pos()],
));
@@ -140,7 +155,7 @@ pub async fn gen_match_macro_lib() -> Vec<GenMember> {
.rule(mactreev!("pattern::match_rule"("pattern::ref" "$" name)), async |_cx, [name]| {
let MacTok::Name(name) = name.tok() else {
return Err(mk_errv(
is("pattern 'ref' requires a name to bind to").await,
is!("pattern 'ref' requires a name to bind to"),
format!(
"'ref' was interpreted as a binding matcher, \
but it was followed by {} instead of a name",
+1 -1
View File
@@ -50,7 +50,7 @@ impl Lexer for PhLexer {
match prio_s.parse::<u8>() {
Ok(priority) => (tail, PhKind::Vector { at_least_one, priority }),
Err(e) =>
return Err(mk_errv(is("Invalid placeholder priority").await, e.to_string(), [
return Err(mk_errv(is!("Invalid placeholder priority"), e.to_string(), [
ctx.pos_tt(line, tail)
])),
}
+1 -1
View File
@@ -185,7 +185,7 @@ async fn resolve_seq(
Err((lran, rran))
}
});
let mac_conflict_tk = is("Macro conflict").await;
let mac_conflict_tk = is!("Macro conflict");
let error = conflict_sets
.filter(|r| 1 < r.len())
.map(|set| {
+5 -5
View File
@@ -122,7 +122,7 @@ async fn mk_scalar(pattern: &MacTree) -> OrcRes<ScalMatcher> {
MacTok::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(&body.items).boxed_local().await?)),
MacTok::Lambda(..) => {
return Err(mk_errv(
is("Lambda in matcher").await,
is!("Lambda in matcher"),
"Lambdas can't be matched for, only generated in templates",
[pattern.pos()],
));
@@ -150,7 +150,7 @@ mod test {
let pattern = vec![
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: is("::prefix").await,
name: is!("::prefix"),
}))
.await,
ex(MacTok::Name(sym!(prelude::do))).await,
@@ -159,13 +159,13 @@ mod test {
MacTreeSeq::new([
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: is("expr").await,
name: is!("expr"),
}))
.await,
ex(MacTok::Name(sym!(prelude::;))).await,
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 1, at_least_one: false },
name: is("rest").await,
name: is!("rest"),
}))
.await,
]),
@@ -173,7 +173,7 @@ mod test {
.await,
ex(MacTok::Ph(Ph {
kind: PhKind::Vector { priority: 0, at_least_one: false },
name: is("::suffix").await,
name: is!("::suffix"),
}))
.await,
];
+2 -2
View File
@@ -22,12 +22,12 @@ impl Matcher {
let first = pattern.first().expect("Empty pattern is not allowed");
if vec_attrs(first).is_none() {
let pos = first.pos();
pattern.insert(0, MacTok::Ph(Ph { name: is("::before").await, kind }).at(pos));
pattern.insert(0, MacTok::Ph(Ph { name: is!("::before"), kind }).at(pos));
}
let last = pattern.last().expect("first returned Some above");
if vec_attrs(last).is_none() {
let pos = last.pos();
pattern.insert(0, MacTok::Ph(Ph { name: is("::after").await, kind }).at(pos));
pattern.insert(0, MacTok::Ph(Ph { name: is!("::after"), kind }).at(pos));
}
Ok(Matcher { inner: mk_any(&pattern).await? })
}
+1 -1
View File
@@ -6,7 +6,7 @@ pub async fn gen_functional_macro_lib() -> Vec<GenMember> {
prefix("std::fn", [
build_macro(Some(50), ["|>"])
.rule(
mactreev!("...$" lhs 0 "std::fn::|>" "$" fun "...$" rhs 0),
mactreev!("...$" lhs 0 "std::fn::|>" "$" fun "..$" rhs 0),
async |mut cx, [lhs, fun, rhs]| {
Ok(cx.recur(mactree!("push" fun ; "push" lhs ; "pushv" rhs)).await)
},
+4 -4
View File
@@ -26,11 +26,11 @@ pub async fn gen_result_macro_lib() -> Vec<GenMember> {
mactreev!("pattern::match_rule" ( "std::result::ok" "...$" sub_pattern 0)),
async |mut cx, [sub]| {
let sub: TAtom<MatcherAtom> =
cx.recur_call(mactree!("pattern::match_rule" "push" sub;)).await?;
cx.recur_call(mactree!("pattern::match_rule" "push" sub)).await?;
let sub = sub.own().await;
let matcher = new_atom(MatcherAtom {
keys: sub.keys,
matcher: mactree!("std::option::is_some_body" "push" sub.matcher),
matcher: mactree!("std::result::is_ok_body" "push" sub.matcher),
});
Ok(mactree!("Val" matcher))
},
@@ -39,11 +39,11 @@ pub async fn gen_result_macro_lib() -> Vec<GenMember> {
mactreev!("pattern::match_rule" ( "std::result::err" "...$" sub_pattern 0)),
async |mut cx, [sub]| {
let sub: TAtom<MatcherAtom> =
cx.recur_call(mactree!("pattern::match_rule" "push" sub;)).await?;
cx.recur_call(mactree!("pattern::match_rule" "push" sub)).await?;
let sub = sub.own().await;
let matcher = new_atom(MatcherAtom {
keys: sub.keys,
matcher: mactree!("std::option::is_some_body" "push" sub.matcher),
matcher: mactree!("std::result::is_err_body" "push" sub.matcher),
});
Ok(mactree!("Val" matcher))
},
+8 -2
View File
@@ -1,3 +1,4 @@
use std::any::type_name_of_val;
use std::borrow::Cow;
use std::rc::Rc;
@@ -40,7 +41,7 @@ impl OwnedAtom for MacroBodyArgCollector {
if !self.args.is_empty() {
eprintln!("This is an intermediary value. It should never be copied");
}
self.clone().call(arg).await
self.clone().call(arg).await.to_gen().await
}
async fn call(mut self, arg: orchid_extension::Expr) -> GExpr {
let atom = (TAtom::<MacTree>::downcast(arg.handle()).await).unwrap_or_else(|_| {
@@ -124,7 +125,12 @@ impl MacroBuilder {
let arr =
argv.into_iter().collect_array::<N>().expect("argc should enforce the length");
let body = body.clone();
Box::pin(async move { body(rec, arr).await.map(new_atom).to_gen().await })
Box::pin(async move {
eprintln!("Entering {}", type_name_of_val(&body));
let ret = body(rec, arr).await.map(new_atom).to_gen().await;
eprintln!("exiting {}", type_name_of_val(&body));
ret
})
}),
})
})
+3 -3
View File
@@ -15,7 +15,7 @@ async fn bounds_error(
args: impl IntoIterator<Item = usize>,
) -> OrcErrv {
mk_errv(
is("Index out of bounds").await,
is!("Index out of bounds"),
format!("Selected {expected} from blob of len {}", blob.0.len()),
get_arg_posv(args).await,
)
@@ -98,7 +98,7 @@ pub fn gen_binary_lib() -> Vec<GenMember> {
}
if 8 < len {
return Err(mk_errv(
is("Too many bytes for int conversion").await,
is!("Too many bytes for int conversion"),
format!("At most 8 bytes fit into an Int, requested {len}"),
get_arg_posv(3..4).await,
));
@@ -124,7 +124,7 @@ pub fn gen_binary_lib() -> Vec<GenMember> {
fun(true, "from_num", async |len: Int, le: Bool, val: Int| {
if 8 < len.0 {
return Err(mk_errv(
is("Too many bytes for int conversion").await,
is!("Too many bytes for int conversion"),
format!("Ints are 8 bytes, attempted to write {} byte buffer", len.0),
get_arg_posv(0..1).await,
));
+1 -1
View File
@@ -8,7 +8,7 @@ use crate::OrcString;
pub fn gen_bottom_lib() -> Vec<GenMember> {
prefix("std", [
fun(true, "panic", async |msg: OrcString| {
bot(mk_errv(is("Panic!").await, &*msg.get_string().await, get_arg_posv([0, 1]).await))
bot(mk_errv(is!("Panic!"), &*msg.get_string().await, get_arg_posv([0, 1]).await))
}),
fun(true, "seq", async |precond: Expr, value: Expr| seq(precond, value)),
])
+2 -2
View File
@@ -49,7 +49,7 @@ impl Supports<ProtocolMethod> for Int {
match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, sym!(std::number::Int).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await;
let name = Sym::from_api(key).await.expect("Protocol name should be statically interned");
let val = if name == sym!(std::ops::add) {
sym!(std::number::add)
} else if name == sym!(std::ops::sub) {
@@ -108,7 +108,7 @@ impl Supports<ProtocolMethod> for Float {
match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, sym!(std::number::Float).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await;
let name = Sym::from_api(key).await.expect("Protocol name should be statically interned");
let val = if name == sym!(std::ops::add) {
sym!(std::number::add)
} else if name == sym!(std::ops::sub) {
+1 -1
View File
@@ -45,7 +45,7 @@ pub fn gen_num_lib() -> Vec<GenMember> {
Some(i) => i,
None => {
return Err(mk_errv(
is("Float out of range").await,
is!("Float out of range"),
format!("{f} is not representable as an integer"),
[get_arg(0).pos().await],
));
+1 -1
View File
@@ -19,7 +19,7 @@ impl Lexer for SubscriptLexer {
};
let new_tail = &tail[name_len + 1..];
Ok((new_tail, [
GenTok::Name(is(".").await).at(lctx.pos_lt(1, &tail[1..])),
GenTok::Name(is!(".")).at(lctx.pos_lt(1, &tail[1..])),
GenTok::NewExpr(new_atom(IntStrAtom(is(&tail[1..name_len + 1]).await)))
.at(lctx.pos_tt(&tail[1..], new_tail)),
]))
+1 -1
View File
@@ -62,7 +62,7 @@ pub fn gen_option_lib() -> Vec<GenMember> {
fun(true, "expect", async |opt: OrcOpt<Expr>, msg: OrcString| match opt {
OrcOpt(Some(ex)) => Ok::<Expr, _>(ex),
OrcOpt(None) => Err(mk_errv(
is("Unwrapped std::option::none").await,
is!("Unwrapped std::option::none"),
msg.get_string().await.as_str(),
get_arg_posv([0]).await,
)),
+8 -12
View File
@@ -16,7 +16,7 @@ pub async fn parse_impls(
Token::S(Paren::Round, body) => line_items(Snippet::new(body_tt, body)).await,
Token::S(ptyp, _) => {
return Err(mk_errv(
is("Incorrect paren type").await,
is!("Incorrect paren type"),
format!("Expected () block, found {ptyp}"),
[body_tt.sr().pos()],
));
@@ -29,7 +29,7 @@ pub async fn parse_impls(
},
};
for Parsed { tail: line, output: comments } in body {
if let Ok(Parsed { tail, .. }) = expect_tok(line, is("impl").await).await {
if let Ok(Parsed { tail, .. }) = expect_tok(line, is!("impl")).await {
let Parsed { tail, output: name_tt } = parse_multiname(tail).await?;
let (name, name_sr) = match name_tt.into_iter().at_most_one() {
Ok(None) => panic!("multiname is always at least one name"),
@@ -37,38 +37,34 @@ pub async fn parse_impls(
(n.clone().mspath().to_sym().await, sr.clone()),
Ok(Some(Import { name: None, sr, .. })) => {
return Err(mk_errv(
is("impl line with globstar").await,
is!("impl line with globstar"),
"::* is not permitted in a protocol impl",
[sr.pos()],
));
},
Err(e) => {
return Err(mk_errv(
is("Impl line with multiple protocol names").await,
is!("Impl line with multiple protocol names"),
"::() is not permitted in a protocol impl",
e.map(|i| i.sr.pos()),
));
},
};
let Parsed { tail, .. } = expect_tok(tail, is("as").await).await?;
let Parsed { tail, .. } = expect_tok(tail, is!("as")).await?;
let cnst_name = is(&format!("{}{}", lines.len(), name.iter().join("__"))).await;
lines.push(ParsedLine {
comments,
sr: line.sr(),
kind: ParsedLineKind::Rec(Vec::from_iter(chain![
[Token::Name(is("let").await).at(line.sr())],
[Token::Name(is!("let")).at(line.sr())],
[Token::Name(cnst_name.clone()).at(name_sr)],
[Token::Name(is("=").await).at(line.sr())],
[Token::Name(is!("=")).at(line.sr())],
tail.iter().cloned(),
])),
});
impls.push((name, cnst_name));
} else {
lines.push(ParsedLine {
sr: line.sr(),
comments,
kind: ParsedLineKind::Rec(line.to_vec()),
});
lines.push(ParsedLine { sr: line.sr(), comments, kind: ParsedLineKind::Rec(line.to_vec()) });
}
}
Ok(())
+4 -4
View File
@@ -23,7 +23,7 @@ impl Parser for AsProtoParser {
expect_end(tail).await?;
if exported {
return Err(mk_errv(
is("Exported internal line").await,
is!("Exported internal line"),
"as_proto cannot be exported, the type shares the enclosing module's visibility",
[line.sr().pos()],
));
@@ -32,7 +32,7 @@ impl Parser for AsProtoParser {
let mut impls = Vec::new();
parse_impls(&pcx, &mut lines, &mut impls, body_tt).await?;
let id = pcx.module();
let proto_tag_name = is("__protocol_tag__").await;
let proto_tag_name = is!("__protocol_tag__");
let proto_tag_path = id.suffix([proto_tag_name.clone()]).await;
let mut new_impls = HashMap::new();
for (k, v) in impls {
@@ -41,7 +41,7 @@ impl Parser for AsProtoParser {
lines.push(ParsedLine::cnst(&line.sr(), &cmts, true, proto_tag_name, vec![
x_tok(new_atom(Tag { id, impls: Rc::new(new_impls) })).await.at(line.sr()),
]));
lines.push(ParsedLine::cnst(&line.sr(), [], false, is("resolve").await, vec![
lines.push(ParsedLine::cnst(&line.sr(), [], false, is!("resolve"), vec![
x_tok(call(sym!(std::protocol::resolve), proto_tag_path)).await.at(line.sr()),
]));
Ok(lines)
@@ -60,7 +60,7 @@ impl Parser for ProtoParser {
) -> OrcRes<Vec<ParsedLine>> {
let Parsed { output: name_tt, tail } = try_pop_no_fluff(line).await?;
let Token::Name(name) = &name_tt.tok else {
return Err(mk_errv(is("missing name for type").await, "A type needs a name", [name_tt
return Err(mk_errv(is!("missing name for type"), "A type needs a name", [name_tt
.sr()
.pos()]));
};
+5 -5
View File
@@ -23,7 +23,7 @@ impl Parser for AsTypeParser {
expect_end(tail).await?;
if exported {
return Err(mk_errv(
is("Exported internal line").await,
is!("Exported internal line"),
"as_type cannot be exported, the type shares the enclosing module's visibility",
[line.sr().pos()],
));
@@ -32,7 +32,7 @@ impl Parser for AsTypeParser {
let mut impls = Vec::new();
parse_impls(&ctx, &mut lines, &mut impls, body_tt).await?;
let id = ctx.module();
let type_tag_name = is("__type_tag__").await;
let type_tag_name = is!("__type_tag__");
let type_tag_path = id.suffix([type_tag_name.clone()]).await;
let mut new_impls = HashMap::new();
for (k, v) in impls {
@@ -43,11 +43,11 @@ impl Parser for AsTypeParser {
x_tok(new_atom(Tag { id, impls: Rc::new(new_impls) })).await.at(line.sr()),
]));
let type_tag_path_1 = type_tag_path.clone();
lines.push(ParsedLine::cnst(&line.sr(), [], false, is("wrap").await, vec![
lines.push(ParsedLine::cnst(&line.sr(), [], false, is!("wrap"), vec![
x_tok(call(sym!(std::protocol::wrap), type_tag_path_1)).await.at(line.sr()),
]));
let type_tag_path_1 = type_tag_path.clone();
lines.push(ParsedLine::cnst(&line.sr(), [], false, is("unwrap").await, vec![
lines.push(ParsedLine::cnst(&line.sr(), [], false, is!("unwrap"), vec![
x_tok(call(sym!(std::protocol::unwrap), type_tag_path_1)).await.at(line.sr()),
]));
Ok(lines)
@@ -66,7 +66,7 @@ impl Parser for TypeParser {
) -> OrcRes<Vec<ParsedLine>> {
let Parsed { output: name_tt, tail } = try_pop_no_fluff(line).await?;
let Token::Name(name) = &name_tt.tok else {
return Err(mk_errv(is("missing name for type").await, "A type needs a name", [name_tt
return Err(mk_errv(is!("missing name for type"), "A type needs a name", [name_tt
.sr()
.pos()]));
};
+14 -14
View File
@@ -43,10 +43,10 @@ impl Supports<ProtocolMethod> for Tag {
) -> std::io::Result<orchid_base::Receipt> {
match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, self.id.to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) =>
hand
.reply(req, self.impls.get(&Sym::from_api(key).await).map(|expr| expr.handle().ticket()))
.await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let key = Sym::from_api(key).await.expect("All impl keys should be statically interned");
hand.reply(req, self.impls.get(&key).map(|expr| expr.handle().ticket())).await
},
}
}
}
@@ -99,14 +99,14 @@ impl Supports<ProtocolMethod> for Tagged {
pub async fn get_impl(receiver: ForeignAtom, proto: ForeignAtom) -> OrcRes<Expr> {
let Some(proto_id) = proto.call(GetTagId).await else {
return Err(mk_errv(
is("Not a protocol").await,
is!("Not a protocol"),
format!("Protocol ({}) does not have a tag ID", fmt(&proto).await),
[proto.pos()],
));
};
let Some(impl_val_opt) = receiver.call(GetImpl(proto_id)).await else {
return Err(mk_errv(
is("Receiver not tagged").await,
is!("Receiver not tagged"),
format!("The receiver ({}) does not have a type tag", fmt(&receiver).await),
[receiver.pos()],
));
@@ -116,14 +116,14 @@ pub async fn get_impl(receiver: ForeignAtom, proto: ForeignAtom) -> OrcRes<Expr>
}
let Some(type_id) = receiver.call(GetTagId).await else {
return Err(mk_errv(
is("Incorrect protocols implementation in extension").await,
is!("Incorrect protocols implementation in extension"),
format!("The receiver ({}) provides an impl table but no tag ID", fmt(&receiver).await),
[receiver.pos()],
));
};
let Some(impl_val_opt) = proto.call(GetImpl(type_id)).await else {
return Err(mk_errv(
is("Incorrect protocols implementation in extension").await,
is!("Incorrect protocols implementation in extension"),
format!("Protocol ({}) provides a tag ID but no impl table", fmt(&proto).await),
[receiver.pos()],
));
@@ -131,11 +131,11 @@ pub async fn get_impl(receiver: ForeignAtom, proto: ForeignAtom) -> OrcRes<Expr>
if let Some(impl_val) = impl_val_opt {
return Ok(Expr::deserialize(impl_val).await);
}
return Err(mk_errv(
is("Implementation not found").await,
format!("Protocol {} is not implemented for {}", ev(proto_id).await, ev(type_id).await),
Err(mk_errv(
is!("Implementation not found"),
format!("Protocol {:?} is not implemented for {:?}", ev(proto_id).await, ev(type_id).await),
[receiver.pos(), proto.pos()],
));
))
}
pub fn gen_protocol_lib() -> Vec<GenMember> {
@@ -153,7 +153,7 @@ pub fn gen_protocol_lib() -> Vec<GenMember> {
Ok(own_val.value)
} else {
Err(mk_errv(
is("Type mismatch").await,
is!("Type mismatch"),
format!("{} has type {}, expected {}", fmt(&value).await, own_val.tag.id, own_tag.id),
[value.pos()],
))
@@ -254,7 +254,7 @@ pub fn resolver_for(proto: VName) -> impl AsyncFn(ForeignAtom) -> GExpr + Clone
Some(val) => val,
None => {
let proto: ForeignAtom =
h.exec(proto.clone().suffix([is("__protocol_tag__").await]).to_sym().await).await?;
h.exec(proto.clone().suffix([is!("__protocol_tag__")]).to_sym().await).await?;
*proto_cache.borrow_mut() = Some(proto.clone());
proto
},
+9 -3
View File
@@ -32,8 +32,14 @@ impl OwnedAtom for RecordAtom {
values
}
async fn deserialize(mut dctx: impl DeserializeCtx, refs: Self::Refs) -> Self {
let keys =
join_all(dctx.decode::<Vec<api::TStr>>().await.iter().map(|t| async { es(*t).await })).await;
let keys = join_all(
dctx
.decode::<Vec<api::TStr>>()
.await
.iter()
.map(|t| async { es(*t).await.expect("Server lost interned keys during serialization") }),
)
.await;
RecordAtom(Rc::new(keys.into_iter().zip(refs).collect()))
}
@@ -44,7 +50,7 @@ impl Supports<ProtocolMethod> for RecordAtom {
match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, sym!(std::record::Record).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await;
let name = Sym::from_api(key).await.expect("Protocol name should be statically interned");
let val = if name == sym!(std::ops::get) {
sym!(std::record::get)
} else if name == sym!(std::ops::set) {
+1 -1
View File
@@ -23,7 +23,7 @@ pub fn gen_record_lib() -> Vec<GenMember> {
match record.0.get(&key.0) {
Some(val) => Ok(val.clone()),
None => Err(mk_errv(
is("Key not found in record").await,
is!("Key not found in record"),
format!("{} is not in this record, valid keys are {}", key.0, record.0.keys().join(", ")),
get_arg_posv([0, 1]).await,
)),
+3 -3
View File
@@ -2,7 +2,7 @@ use std::borrow::Cow;
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use orchid_base::{NameLike, ReqHandleExt, Sym, es, is, mk_errv};
use orchid_base::{NameLike, ReqHandleExt, Sym, is, mk_errv};
use orchid_extension::gen_expr::new_atom;
use orchid_extension::tree::{GenMember, fun, prefix};
use orchid_extension::{
@@ -52,10 +52,10 @@ pub async fn sym_expr(sym: Sym) -> Expr {
pub async fn gen_sym_lib() -> Vec<GenMember> {
prefix("std::refl::sym", [
fun(true, "from_str", async move |str: TAtom<IntStrAtom>| {
match Sym::parse(&es(*str).await).await {
match Sym::parse(&str.e().await).await {
Ok(sym) => Ok(new_atom(SymAtom(sym))),
Err(_) => Err(mk_errv(
is("Cannot parse sym from empty string").await,
is!("Cannot parse sym from empty string"),
"Empty string passed to std::refl::sym::from_str",
[str.pos()],
)),
+1 -1
View File
@@ -69,7 +69,7 @@ pub fn gen_result_lib() -> Vec<GenMember> {
fun(true, "expect", async |res: OrcResObj<Expr, Expr>, msg: OrcString| match res {
OrcResObj(Ok(ex)) => Ok::<Expr, _>(ex),
OrcResObj(Err(ex)) => Err(mk_errv(
is("Unwrapped std::result::err").await,
is!("Unwrapped std::result::err"),
format!("Unexpected error {}: {}", fmt(&ex).await, msg.get_string().await.as_str()),
get_arg_posv([0]).await,
)),
+13 -9
View File
@@ -97,25 +97,29 @@ impl System for StdSystem {
xreq.reply(req, tk).await.unwrap()
},
StdReq::CreateRecord(ref req @ CreateRecord(ref items)) => {
let values =
join_all(items.iter().map(async |(k, v)| (es(*k).await, Expr::deserialize(*v).await)))
.await;
let values = join_all(items.iter().map(async |(k, v)| {
(es(*k).await.expect("Dropped keys while CreateRecord"), Expr::deserialize(*v).await)
}))
.await;
let rec = RecordAtom(Rc::new(values.into_iter().collect()));
let tk = new_atom(rec).to_expr().await.serialize().await;
xreq.reply(req, tk).await.unwrap()
},
StdReq::CreateSymAtom(ref req @ CreateSymAtom(sym_tok)) => {
let sym_atom = SymAtom(Sym::from_api(sym_tok).await);
let sym_atom =
SymAtom(Sym::from_api(sym_tok).await.expect("Dropped sym to be turned into SymAtom"));
xreq.reply(req, new_atom(sym_atom).to_expr().await.serialize().await).await.unwrap()
},
StdReq::CreateTag(ref req @ CreateTag { name, ref impls }) => {
let tag_atom = Tag {
id: Sym::from_api(name).await,
id: Sym::from_api(name).await.expect("Dropped type tag during CreateTag"),
impls: Rc::new(
join_all(
(impls.iter())
.map(|(k, v)| async { (Sym::from_api(*k).await, Expr::deserialize(*v).await) }),
)
join_all((impls.iter()).map(|(k, v)| async {
(
Sym::from_api(*k).await.expect("Dropped impl target tag during CreateTag"),
Expr::deserialize(*v).await,
)
}))
.await
.into_iter()
.collect(),
+2 -2
View File
@@ -37,7 +37,7 @@ impl Supports<StartCommand> for ReadStreamCmd {
let ret = 'ret: {
let Some(read_res) = self.hand.call(ReadReq { limit: self.limit.clone() }).await else {
break 'ret Err(mk_errv(
is("Atom is not readable").await,
is!("Atom is not readable"),
format!("Expected a readable stream handle, found {}", fmt(&self.hand).await),
[self.hand.pos()],
));
@@ -90,7 +90,7 @@ impl Supports<StartCommand> for WriteStreamCmd {
};
let cont = match result {
None => bot(mk_errv(
is("Not a writer").await,
is!("Not a writer"),
format!("{} cannot be written to", fmt(&self.hand).await),
[self.hand.pos()],
)),
+2 -2
View File
@@ -27,7 +27,7 @@ pub fn gen_stream_lib() -> Vec<GenMember> {
fun(true, "read_until", async |hand: ForeignAtom, delim: Int, cont: Expr| {
let Ok(end) = delim.0.try_into() else {
return Err(mk_errv(
is("Byte out of range").await,
is!("Byte out of range"),
format!(
"{} doesn't fit into a byte and cannot be used as a delimiter for reading",
delim.0
@@ -47,7 +47,7 @@ pub fn gen_stream_lib() -> Vec<GenMember> {
Ok(None) =>
Ok(call(cont, OrcResObj::<_, Never>(Ok(new_atom(BlobAtom(Rc::default()))))).await),
Err(_) => Err(mk_errv(
is("Length cannot be negative").await,
is!("Length cannot be negative"),
format!("{} is negative and cannot be used as a length", count.0),
[get_arg(1).pos().await],
)),
+15 -7
View File
@@ -74,7 +74,7 @@ impl Supports<ProtocolMethod> for StrAtom {
match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, sym!(std::string::StrAtom).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await;
let name = Sym::from_api(key).await.expect("Protocol name should be statically interned");
let val = if name == sym!(std::ops::add) {
sym!(std::string::concat)
} else {
@@ -86,11 +86,19 @@ impl Supports<ProtocolMethod> for StrAtom {
}
}
#[derive(Clone, Coding)]
pub struct AtomicIntStr(pub orchid_api::TStr);
impl AtomicIntStr {
pub async fn e(&self) -> IStr {
es(self.0).await.expect("IntStrAtom should keep alive the string")
}
}
#[derive(Debug, Clone)]
pub struct IntStrAtom(pub(crate) IStr);
impl Atomic for IntStrAtom {
type Variant = OwnedVariant;
type Data = orchid_api::TStr;
type Data = AtomicIntStr;
fn reg_methods() -> MethodSetBuilder<Self> {
MethodSetBuilder::new().handle::<ProtocolMethod>().handle::<ToStringMethod>()
}
@@ -100,7 +108,7 @@ impl From<IStr> for IntStrAtom {
}
impl OwnedAtom for IntStrAtom {
type Refs = ();
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(self.0.to_api()) }
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(AtomicIntStr(self.0.to_api())) }
async fn print_atom<'a>(&'a self, _: &'a (impl FmtCx + ?Sized + 'a)) -> FmtUnit {
format!("{:?}i", &*self.0).into()
}
@@ -114,7 +122,7 @@ impl OwnedAtom for IntStrAtom {
}
impl TryFromExpr for IntStrAtom {
async fn try_from_expr(expr: Expr) -> OrcRes<Self> {
Ok(IntStrAtom(es(TAtom::<IntStrAtom>::try_from_expr(expr).await?.value).await))
Ok(IntStrAtom(TAtom::<IntStrAtom>::try_from_expr(expr).await?.value.e().await))
}
}
impl Supports<ToStringMethod> for IntStrAtom {
@@ -128,7 +136,7 @@ impl Supports<ProtocolMethod> for IntStrAtom {
ProtocolMethod::GetTagId(req) =>
hand.reply(&req, sym!(std::string::IntStrAtom).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await;
let name = Sym::from_api(key).await.expect("Protocol name should be statically interned");
let val = if name == sym!(std::ops::add) {
sym!(std::string::concat)
} else {
@@ -152,7 +160,7 @@ pub enum OrcStringKind {
impl OrcString {
pub async fn get_string(&self) -> Rc<String> {
match &self.kind {
OrcStringKind::Int(tok) => es(**tok).await.rc(),
OrcStringKind::Int(tok) => tok.e().await.rc(),
OrcStringKind::Val(atom) => atom.call(StringGetValMethod).await,
}
}
@@ -165,7 +173,7 @@ impl TryFromExpr for OrcString {
}
match TAtom::<IntStrAtom>::try_from_expr(expr).await {
Ok(t) => Ok(OrcString { kind: OrcStringKind::Int(t) }),
Err(e) => Err(mk_errv(is("A string was expected").await, "", e.pos_iter())),
Err(e) => Err(mk_errv(is!("A string was expected"), "", e.pos_iter())),
}
}
}
+2 -3
View File
@@ -31,7 +31,7 @@ impl StringError {
pub async fn into_proj(self, path: &Sym, pos: u32) -> OrcErrv {
let start = pos + self.pos;
mk_errv(
is("Failed to parse string").await,
is!("Failed to parse string"),
match self.kind {
StringErrorKind::NotHex => "Expected a hex digit",
StringErrorKind::BadCodePoint => "The specified number is not a Unicode code point",
@@ -121,7 +121,6 @@ impl Lexer for StringLexer {
};
loop {
if let Some(rest) = tail.strip_prefix('"') {
eprintln!("From {} remaining tokens are {}", all.len(), rest.len());
return Ok((
rest,
add_frag(ret, str_to_gen(&mut cur, tail, &mut errors, lctx).await).await,
@@ -148,7 +147,7 @@ impl Lexer for StringLexer {
tail = ch.as_str();
} else {
let range = lctx.pos(all)..lctx.pos("");
return Err(mk_errv(is("No string end").await, "String never terminated with \"", [
return Err(mk_errv(is!("No string end"), "String never terminated with \"", [
SrcRange::new(range.clone(), lctx.src()),
]));
}
+3 -3
View File
@@ -62,7 +62,7 @@ pub fn gen_str_lib() -> Vec<GenMember> {
let Some(s) = substr_iter.next() else {
let str_len = str.graphemes(true).count();
return Err(mk_errv(
is("Index out of bounds").await,
is!("Index out of bounds"),
format!("Tried to select grapheme {start}+{len} from string that only has {str_len}"),
[get_arg(0).pos().await, get_arg(1).pos().await, get_arg(2).pos().await],
));
@@ -108,7 +108,7 @@ pub fn gen_str_lib() -> Vec<GenMember> {
let Some((i, _)) = str.grapheme_indices(true).nth(i.0.try_into().unwrap()) else {
let len = str.graphemes(true).count();
return Err(mk_errv(
is("Index out of bounds").await,
is!("Index out of bounds"),
format!("Tried to split string at {}, it only has {} graphemes", i.0, len),
[get_arg(0).pos().await, get_arg(1).pos().await],
));
@@ -127,7 +127,7 @@ pub fn gen_str_lib() -> Vec<GenMember> {
let Some(s) = str.graphemes(true).nth(i.0.try_into().unwrap()) else {
let len = str.graphemes(true).count();
return Err(mk_errv(
is("Index out of bounds").await,
is!("Index out of bounds"),
format!("Tried to read grapheme {} from string, it only has {}", i.0, len),
[get_arg(0).pos().await, get_arg(1).pos().await],
));
+8 -8
View File
@@ -56,7 +56,7 @@ impl Supports<ProtocolMethod> for Tuple {
match req {
ProtocolMethod::GetTagId(req) => hand.reply(&req, sym!(std::tuple).to_api()).await,
ProtocolMethod::GetImpl(ref req @ GetImpl(key)) => {
let name = Sym::from_api(key).await;
let name = Sym::from_api(key).await.expect("Protocol name should be statically interned");
let val = if name == sym!(std::ops::get) {
sym!(std::tuple::get)
} else if name == sym!(std::ops::set) {
@@ -116,11 +116,11 @@ pub fn gen_tuple_lib() -> Vec<GenMember> {
{
return Ok(val.clone());
}
return Err(mk_errv(
is("Tuple index out of bounds").await,
Err(mk_errv(
is!("Tuple index out of bounds"),
format!("{} is out of bounds for Tuple{}", idx.0, tup.len()),
[idx.pos()],
));
))
}),
fun(true, "set", async |tup: TAtom<Tuple>, idx: TAtom<Int>, val: Expr| {
if let Ok(idx) = usize::try_from(idx.0) {
@@ -130,11 +130,11 @@ pub fn gen_tuple_lib() -> Vec<GenMember> {
return Ok(new_atom(Tuple(Rc::new(new_vec))));
}
}
return Err(mk_errv(
is("Tuple index out of bounds").await,
Err(mk_errv(
is!("Tuple index out of bounds"),
format!("{} is out of bounds for Tuple{}", idx.0, tup.len()),
[idx.pos()],
));
))
}),
fun(true, "len", async |tup: TAtom<Tuple>| {
Int(tup.len().try_into().expect("Tuple was created with an Int length"))
@@ -182,7 +182,7 @@ mod tpl_impls {
let tpl = UntypedTuple::try_from_expr(expr.clone()).await?;
let Some([$( [< $t:lower >], )*]) = tpl.0.iter().cloned().collect_array() else {
return Err(mk_errv(
is("Tuple arity mismatch").await,
is!("Tuple arity mismatch"),
format!("Expected a {}-ary tuple, found {}-ary", $len, tpl.0.len()),
[expr.data().await.pos.clone()]
));
+3 -3
View File
@@ -8,11 +8,11 @@ authors = ["Lawrence Bethlenfalvy <lbfalvy@protonmail.com>"]
[dependencies]
async-fn-stream = { version = "0.1.0", path = "../async-fn-stream" }
camino = "1.2.2"
camino = "1.2.3"
clap = { version = "4.6.1", features = ["derive", "env", "cargo"] }
ctrlc = "3.5.2"
futures = "0.3.32"
itertools = "0.14.0"
itertools = "0.15.0"
never = "0.1.0"
orchid-api = { version = "0.1.0", path = "../orchid-api" }
orchid-base = { version = "0.1.0", path = "../orchid-base" }
@@ -23,5 +23,5 @@ orchid-host = { version = "0.1.0", path = "../orchid-host", features = [
] }
stacker = "0.1.24"
substack = "1.1.1"
tokio = { version = "1.52.1", features = ["full"] }
tokio = { version = "1.52.3", features = ["full"] }
tokio-util = { version = "0.7.18", features = ["compat"] }
+17 -27
View File
@@ -1,5 +1,5 @@
use never::Never;
use orchid_base::{IStr, Logger, Receipt, ReqHandle};
use orchid_base::{IStr, Logger, Receipt, ReqHandle, TokioSpawn};
use orchid_extension::{self as ox, OrcReader, OrcWriter};
use orchid_host::cmd_system::{CmdEvent, CmdRunner};
use orchid_host::dylib::ext_dylib;
@@ -23,15 +23,14 @@ use std::time::Duration;
use async_fn_stream::try_stream;
use camino::{Utf8Path, Utf8PathBuf};
use clap::{Parser, Subcommand};
use futures::future::LocalBoxFuture;
use futures::{FutureExt, Stream, TryStreamExt, io};
use futures::{Stream, TryStreamExt, io};
use itertools::Itertools;
use orchid_base::local_interner::local_interner;
use orchid_base::{
FmtCxImpl, Snippet, SrcRange, Token, VPath, fmt, fmt_v, is, log, sym, take_first,
try_with_reporter, ttv_fmt, with_interner, with_logger, with_reporter, with_stash,
};
use orchid_host::cx::{Cx, JoinHandle, Spawner};
use orchid_host::cx::Cx;
use orchid_host::execute::{ExecCx, ExecResult};
use orchid_host::expr::ExprKind;
use orchid_host::extension::Extension;
@@ -42,7 +41,7 @@ use orchid_host::parsed::{ParsTokTree, ParsedModule};
use orchid_host::subprocess::ext_command;
use orchid_host::system::init_systems;
use substack::Substack;
use tokio::task::{LocalSet, spawn_local};
use tokio::task::LocalSet;
use crate::parse_folder::parse_folder;
use crate::repl::repl;
@@ -102,7 +101,7 @@ pub struct Args {
next_line_help = true,
verbatim_doc_comment
)]
logs: Vec<String>,
log: Vec<String>,
/// Measure and report the timings of various events
#[arg(long, action)]
time: bool,
@@ -238,7 +237,7 @@ fn parse_log_dest(dest: &str) -> orchid_api::LogStrategy {
fn get_logger(args: &Args) -> LoggerImpl {
let mut logger = LoggerImpl::default();
for cmd in &args.logs {
for cmd in &args.log {
match cmd.split_once(":") {
None => {
let strat = parse_log_dest(cmd);
@@ -262,21 +261,6 @@ fn get_logger(args: &Args) -> LoggerImpl {
logger
}
struct JoinHandleImpl(tokio::task::JoinHandle<()>);
impl JoinHandle for JoinHandleImpl {
fn abort(&self) { self.0.abort() }
fn join(self: Box<Self>) -> LocalBoxFuture<'static, ()> {
Box::pin(async { self.0.await.unwrap() })
}
}
struct SpawnerImpl;
impl Spawner for SpawnerImpl {
fn spawn_obj(&self, delay: Duration, fut: LocalBoxFuture<'static, ()>) -> Box<dyn JoinHandle> {
Box::new(JoinHandleImpl(spawn_local(tokio::time::sleep(delay).then(|()| fut))))
}
}
#[derive(Debug, Default)]
struct StdIoSystem;
impl ox::SystemCard for StdIoSystem {
@@ -307,7 +291,11 @@ impl ox::System for StdIoSystem {
}),
ox::tree::fun(true, "get_stdout", async |cb: ox::Expr| {
ox::cmd(async move || {
Some(ox::gen_expr::call(cb.clone(), OrcWriter(tokio::io::stdout().compat_write())))
eprintln!("get_output req");
let rep =
Some(ox::gen_expr::call(cb.clone(), OrcWriter(tokio::io::stdout().compat_write())));
eprintln!("get_output rep");
rep
})
}),
ox::tree::fun(true, "get_stderr", async |cb: ox::Expr| {
@@ -317,6 +305,7 @@ impl ox::System for StdIoSystem {
}),
])
}
async fn prelude(&self) -> Vec<orchid_base::Sym> { vec![sym!(stdio)] }
}
async fn load_proj_if_set(
@@ -340,7 +329,7 @@ async fn add_const_at(
let prefix_sr = SrcRange::zw(pcx.src(), 0);
let mut lexemes = lex(value.clone(), pcx).await.map_err(|e| e.to_string())?;
writeln!(log("debug"), "lexed: {}", fmt_v::<ParsTokTree>(lexemes.iter()).await.join(" ")).await;
let prefix = [is("export").await, is("let").await, name, is("=").await];
let prefix = [is!("export"), is!("let"), name, is!("=")];
lexemes.splice(0..0, prefix.map(|n| Token::Name(n).at(prefix_sr.clone())));
let snippet = Snippet::new(&lexemes[0], &lexemes);
let items = try_with_reporter(parse_item(pcx, Substack::Bottom, vec![], snippet))
@@ -363,7 +352,8 @@ fn main() -> io::Result<ExitCode> {
let logger = get_logger(&args);
let logger2 = logger.clone();
unsafe { STARTUP = Some(Instant::now()) };
let cx = Cx::new(SpawnerImpl, logger2);
let spawner = Rc::new(TokioSpawn::new());
let cx = Cx::new(spawner.clone(), logger2);
let (signal_end_main, on_end_main) = futures::channel::oneshot::channel();
let cx1 = cx.clone();
local_set.spawn_local(async move {
@@ -430,7 +420,7 @@ fn main() -> io::Result<ExitCode> {
add_const_at(
&mut root,
&pcx.with_src(sym!(usercode)),
is("entrypoint").await,
is!("entrypoint"),
is(code).await,
)
.await?;
@@ -465,7 +455,7 @@ fn main() -> io::Result<ExitCode> {
add_const_at(
&mut root,
&pcx.with_src(sym!(usercode)),
is("entrypoint").await,
is!("entrypoint"),
is(main).await,
)
.await?;
+4 -4
View File
@@ -71,7 +71,7 @@ pub async fn repl(args: &Args, extensions: &[Extension], cx: Cx) -> Result<(), S
let add_imports = |items: &mut Vec<Item>, imports: &[Import]| {
items.extend(imports.iter().map(|import| Item::new(import.sr.clone(), import.clone())));
};
if discr.is_kw(is("import").await) {
if discr.is_kw(is!("import")) {
let import_lines = process_lexemes(&lexemes).await?;
imports.extend(import_lines.into_iter().map(|it| match it.kind {
ItemKind::Import(imp) => imp,
@@ -79,9 +79,9 @@ pub async fn repl(args: &Args, extensions: &[Extension], cx: Cx) -> Result<(), S
}));
continue;
}
let mut prefix = vec![is("export").await];
if !discr.is_kw(is("let").await) {
prefix.extend([is("let").await, name.clone(), is("=").await]);
let mut prefix = vec![is!("export")];
if !discr.is_kw(is!("let")) {
prefix.extend([is!("let"), name.clone(), is!("=")]);
}
lexemes.splice(0..0, prefix.into_iter().map(|n| Token::Name(n).at(prefix_sr.clone())));
let mut new_lines = process_lexemes(&lexemes).await?;
+4 -4
View File
@@ -8,13 +8,13 @@ repository = "https://git.lbfalvy.com/Orchid/orchid"
homepage = "https://git.lbfalvy.com/Orchid/orchid"
[dev-dependencies]
futures = "0.3.31"
itertools = "0.14.0"
futures = "0.3.32"
itertools = "0.15.0"
rand = "0.10.1"
rand_chacha = "0.10.0"
test_executors = "0.4.1"
orchid-async-utils = { version = "0.1.0", path = "../orchid-async-utils" }
[dependencies]
futures-io = "0.3.31"
itertools = "0.14.0"
futures-io = "0.3.32"
itertools = "0.15.0"
+8 -2
View File
@@ -7,6 +7,7 @@ use std::path::Path;
use crate::Args;
pub fn check_api_refs(_args: &Args) -> io::Result<()> {
let mut any_failed = false;
walk_wsp(&mut |_| Ok(true), &mut |file| {
if file.path().extension() == Some(OsStr::new("rs")) && file.file_name() != "lib.rs" {
let mut contents = String::new();
@@ -20,10 +21,15 @@ pub fn check_api_refs(_args: &Args) -> io::Result<()> {
continue;
}
let dname = file.path().to_string_lossy().to_string();
eprintln!("orchid_api imported in {dname}:{}:{}", l + 1, c + 1)
eprintln!("orchid_api imported in {dname}:{}:{}", l + 1, c + 1);
any_failed |= true;
}
}
Ok(())
if any_failed {
Err(io::Error::other("orchid_api imports deep in submodules were found"))
} else {
Ok(())
}
})
}
Loaded 100 of 102 files, more files were not shown because too many files have changed in this diff. Show more