diff --git a/Cargo.lock b/Cargo.lock index c17f67f..a3243e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1204,12 +1204,15 @@ dependencies = [ "ctrlc", "futures", "itertools", + "never", "orchid-api", "orchid-base", + "orchid-extension", "orchid-host", "stacker", "substack", "tokio", + "tokio-util", ] [[package]] diff --git a/examples/hello-world/main.orc b/examples/hello-world/main.orc index 90c5fc1..84d421b 100644 --- a/examples/hello-world/main.orc +++ b/examples/hello-world/main.orc @@ -2,3 +2,8 @@ let user = r[ "foo" 1, "bar" t[3, 4] ] let _main = user.bar.1 let main = "foo" + string::slice "hello" 1 3 + "bar" + +let io_main = ( + stdio::get_stdout \stdout + -- TODO: missing output commands in std +) \ No newline at end of file diff --git a/orchid-base/src/parse.rs b/orchid-base/src/parse.rs index 6d8b099..e365f13 100644 --- a/orchid-base/src/parse.rs +++ b/orchid-base/src/parse.rs @@ -113,7 +113,6 @@ pub struct Comment { pub sr: SrcRange, } impl Comment { - // XXX: which of these four are actually used? pub async fn from_api(c: &api::Comment, src: Sym) -> Self { Self { text: es(c.text).await, sr: SrcRange::new(c.range.clone(), &src) } } @@ -123,9 +122,6 @@ impl Comment { _ => None, } } - pub fn to_tk(&self) -> TokTree { - TokTree { tok: Token::Comment(self.text.clone()), sr: self.sr.clone() } - } pub fn to_api(&self) -> api::Comment { api::Comment { range: self.sr.range(), text: self.text.to_api() } } diff --git a/orchid-extension/src/streams.rs b/orchid-extension/src/streams.rs index 3a7d498..3fab669 100644 --- a/orchid-extension/src/streams.rs +++ b/orchid-extension/src/streams.rs @@ -18,7 +18,7 @@ struct WriterState { writer: Pin>, } -pub struct OrcWriter(T); +pub struct OrcWriter(pub T); impl ToExpr for OrcWriter { async fn to_gen(self) -> GExpr { new_atom(WriterAtom(Rc::new(Mutex::new(WriterState { @@ -61,7 +61,7 @@ impl Supports for WriterAtom { } } -pub struct OrcReader(T); +pub struct OrcReader(pub T); impl ToExpr for OrcReader { async fn to_gen(self) -> GExpr { new_atom(ReaderAtom(Rc::new(Mutex::new(BufReader::new(Box::pin(self.0)))))) diff --git a/orchid-extension/src/system.rs b/orchid-extension/src/system.rs index 67a7e85..7bc3c33 100644 --- a/orchid-extension/src/system.rs +++ b/orchid-extension/src/system.rs @@ -16,10 +16,10 @@ pub type ReqForSystem = as SystemCard>::Req; /// System as defined by author pub trait System: Debug + 'static { type Ctor: SystemCtor; - fn prelude(&self) -> impl Future>; - fn env(&self) -> impl Future>; - fn lexers(&self) -> Vec; - fn parsers(&self) -> Vec; + fn prelude(&self) -> impl Future> { futures::future::ready(Vec::new()) } + fn env(&self) -> impl Future> { futures::future::ready(Vec::new()) } + fn lexers(&self) -> Vec { Vec::new() } + fn parsers(&self) -> Vec { Vec::new() } fn request<'a>( &self, hand: Box + 'a>, diff --git a/orchid-std/src/macros/resolve.rs b/orchid-std/src/macros/resolve.rs index 8111ff2..ed709b2 100644 --- a/orchid-std/src/macros/resolve.rs +++ b/orchid-std/src/macros/resolve.rs @@ -13,61 +13,6 @@ use crate::macros::mactree::MacTreeSeq; use crate::macros::rule::state::{MatchState, StateEntry}; use crate::{MacTok, MacTree}; -/// # TODO -/// -/// convert macro system to return MacTree or otherwise bring it up to -/// speed with the new [ToExpr] / [GExpr] division -/// -/// Idea: MacTree needs to be passed wherever the meaning of an expression can -/// change depending on where in the tree it is bound -/// -/// Idea: lowering MacTree to ToExpr implementors is possible by just knowing -/// what names are bound, not their values, but lowering it to GExpr is not. -/// -/// Problem: The required information is stackbound, so recursive macro matching -/// needs to be a single coroutine. Even when it forks out to Orchid, recursive -/// calls need to point back to this coroutine. Being a coroutine, this -/// recursion can overflow the Rust stack. -/// -/// Limits: -/// -/// - The concrete MacTree being generated sometimes depends on recursive macro -/// calls which need to complete before we return a MacTree -/// - Any placeholders representing expressions must be recursed over before -/// returning in a MacTree -/// - Exactly one of these things must be done on a subtree -/// -/// Takeaways: -/// -/// - Resolution should not lower to GExpr -/// - Consider separate types MacTree vs resolved tree -/// - MacTree can be built for the purpose of passing into recur -/// - Resolved tree can be built for the purpose of returning -/// - cannot contain [...], {...}, (), ( ... \. ) -/// - is pretty much GExpr with sym / dynamic arg binding instead of -/// numbered. Can this be a wrapper type over ToExpr instead? -/// - In order to move recursive state off the stack, we need a loophole -/// for lambdas -/// - Ensures that resolution only happens exactly once which is important -/// because double resolve can produce bugs that are difficult to catch -/// - Macros may return ResolvedTree but they can also return a datastructure -/// containing MacTree -/// - Macros may never lower ResolvedTree to GExpr directly because it may -/// refer to bound arguments by name -/// - Macros returning datastructures can only ever be called as logic while -/// those returning ResolvedTree can only ever be inlined -/// - this is a type system concern so addressing it here is unnecessary -/// -/// Problems: -/// - ToExpr are not usually copiable by default -/// - plain-orchid macros should be able to annotate data-to-return and -/// data-to-resolve with the same tick symbol to limit conceptual complexity, -/// - the case where a macro deliberately wants to bind a name explicitly within -/// a subexpression is tricky -/// -/// The best option probably remains for resolve to process and return MacTree, -/// and for there to be a separate "lower" function. Nothing as yet suggests -/// however that macros can't be allowed to return different types pub async fn resolve(h: &mut ExecHandle<'_>, val: MacTree) -> MacTree { writeln!(log("debug"), "Macro-resolving {}", fmt(&val).await).await; let root = refl(); @@ -290,10 +235,6 @@ async fn resolve_seq( *item = new; any_match = true; } - /* TODO: - The macro system was previously built on the assumption that whatever is returned by call_body - can never be matched. This is no longer true, but now double-matches are possible. Address this. - */ any_match.then_some(MacTreeSeq::new(new_val)) } diff --git a/orchid-std/src/std/future/future_lib.rs b/orchid-std/src/std/future/future_lib.rs deleted file mode 100644 index 352215f..0000000 --- a/orchid-std/src/std/future/future_lib.rs +++ /dev/null @@ -1,349 +0,0 @@ -use std::borrow::Cow; -use std::cell::RefCell; -use std::cmp::{Ordering, Reverse}; -use std::collections::{BinaryHeap, VecDeque}; -use std::fmt::Debug; -use std::mem; -use std::num::NonZeroU64; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll, Waker}; -use std::time::Instant; - -use async_event::Event; -use chrono::TimeDelta; -use futures::channel::{mpsc, oneshot}; -use futures::{FutureExt, select}; -use hashbrown::HashMap; -use never::Never; -use orchid_api_derive::{Coding, Hierarchy}; -use orchid_api_traits::Request; -use orchid_base::{FmtCtxImpl, OrcRes}; -use orchid_extension::ToExpr; -use orchid_extension::entrypoint::spawn; -use orchid_extension::Expr; -use orchid_extension::gen_expr::{GExpr, IntoGExprStream, call, new_atom}; -use orchid_extension::system::cted; -use orchid_extension::tree::{GenMember, cnst, comments, fun, prefix}; -use orchid_extension::{ - Atomic, ForeignAtom, OwnedAtom, OwnedVariant, err_not_callable, err_not_command, -}; -use rust_decimal::prelude::Zero; -use tokio::task::{JoinHandle, spawn_local}; -use tokio::time::sleep; - -use crate::std::std_system::StdReq; -use crate::std::time::OrcDT; -use crate::{StdSystem, api}; - -#[derive(Clone, Copy, Coding, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct AsyncTaskId(NonZeroU64); - -/// Signals to the scheduler that some async work is in progress, and to take -/// ownership of this expression representing the progress of that work. This -/// doesn't have to be called before [FinishAsyncWork] if keeping the work and -/// thus the requesting system alive is not necessary -#[derive(Debug, Clone, Coding, Hierarchy)] -#[extends(FutureReq, StdReq)] -pub struct AddAsyncWork(pub api::ExprTicket); -impl Request for AddAsyncWork { - type Response = AsyncTaskId; -} - -/// Signals to the scheduler that some async work has been finished, and to -/// return this expression from a future `std::future::yield` call. -/// If [AddAsyncWork] was called before this, include the [AsyncTaskId] you -/// received to unlink the work from the scheduler so that cleanup is not -/// blocked. -#[derive(Debug, Clone, Coding, Hierarchy)] -#[extends(FutureReq, StdReq)] -pub struct FinishAsyncWork(pub Option, pub api::ExprTicket); -impl Request for FinishAsyncWork { - type Response = Result<(), SchedulerError>; -} - -#[derive(Debug, Clone, Coding)] -pub struct SchedulerError; - -#[derive(Debug, Clone, Coding, Hierarchy)] -#[extendable] -#[extends(StdReq)] -pub enum FutureReq { - AddAsyncWork(AddAsyncWork), - FinishAsyncWork(FinishAsyncWork), -} - -#[derive(Clone)] -struct Timer { - set_at: Instant, - delay: TimeDelta, - repetition: Option, - cancelled: Rc, - action: Expr, -} -impl Timer { - pub fn next_occurrence(&self) -> Instant { - let delay_mult = i32::try_from(self.repetition.unwrap_or(0) + 1).unwrap(); - self.set_at + (self.delay * delay_mult).to_std().unwrap() - } -} -impl PartialEq for Timer { - fn eq(&self, other: &Self) -> bool { self.next_occurrence().eq(&other.next_occurrence()) } -} -impl Eq for Timer {} -impl PartialOrd for Timer { - fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } -} -impl Ord for Timer { - fn cmp(&self, other: &Self) -> Ordering { self.next_occurrence().cmp(&other.next_occurrence()) } -} -impl Atomic for Timer { - type Variant = OwnedVariant; - type Data = (); -} -impl OwnedAtom for Timer { - type Refs = Never; - async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - async fn command(mut self) -> CmdResult { - let sleep_until = - self.set_at + (self.delay * self.repetition.unwrap_or(1) as i32).to_std().unwrap(); - let (timer_ready, on_timer_ready) = oneshot::channel(); - let task = spawn(self.delay.to_std().unwrap(), async move { mem::drop(timer_ready.send(())) }); - let res = - self.cancelled.wait_until_or_timeout(|| Some(()), on_timer_ready.map(mem::drop)).await; - task.abort(); - // cancelled - if let Some(()) = res { - return Continuation::default().into(); - } - // TODO: add binary API for sleep and - let mut ret = Continuation::default().into(); - let mut ret = vec![self.action.to_gen().await]; - if let Some(rep) = self.repetition.as_mut() { - *rep = *rep + 1; - ret.push(new_atom(self)); - } - Ok(ret) - } -} - -struct SchedulerState { - /// Waker to call when async work finishes - finish_waker: Waker, - timer_task: Option<(Instant, JoinHandle<()>)>, - id: NonZeroU64, - background: HashMap, - ready: VecDeque, - timers: BinaryHeap>, -} -impl SchedulerState { - fn activate_timers(&mut self, now: Instant) { - while let Some(t) = self.timers.peek() - && t.0.next_occurrence() < now - { - let mut timer = self.timers.pop().unwrap().0; - let work = timer.action.clone(); - self.ready.push_back(work); - if let Some(count) = timer.repetition { - timer.repetition = Some(count + 1); - self.timers.push(Reverse(timer)); - } - } - } -} -impl Debug for SchedulerState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("SchedulerState").finish_non_exhaustive() - } -} -impl Default for SchedulerState { - fn default() -> Self { - SchedulerState { - background: HashMap::new(), - finish_waker: Waker::noop().clone(), - id: NonZeroU64::MIN, - timer_task: None, - ready: VecDeque::new(), - timers: BinaryHeap::new(), - } - } -} - -#[derive(Clone, Debug, Default)] -pub struct Scheduler(Rc>); -impl Scheduler { - pub(crate) async fn add(&self, req: &AddAsyncWork) -> ::Response { - let expr = Expr::deserialize(req.0).await; - let mut this = self.0.borrow_mut(); - let id = AsyncTaskId(this.id); - this.background.insert(id, expr); - this.id = this.id.checked_add(1).unwrap(); - id - } - pub(crate) async fn finish( - &self, - req: &FinishAsyncWork, - ) -> ::Response { - let expr = Expr::deserialize(req.1).await; - let mut g = self.0.borrow_mut(); - if let Some(id) = req.0 { - g.background.remove(&id); - } - g.ready.push_back(expr); - g.finish_waker.wake_by_ref(); - Ok(()) - } -} - -#[derive(Clone)] -struct Yield; -impl Atomic for Yield { - type Variant = OwnedVariant; - type Data = (); -} -impl OwnedAtom for Yield { - type Refs = Never; - async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - async fn command(self) -> OrcRes<()> { Ok(()) } -} - -#[derive(Clone)] -struct Spawn(ForeignAtom, ForeignAtom); -impl Atomic for Spawn { - type Variant = OwnedVariant; - type Data = [api::ExprTicket; 2]; -} -impl OwnedAtom for Spawn { - type Refs = Never; - async fn val(&self) -> Cow<'_, Self::Data> { - Cow::Owned([self.0.clone().ex().handle().ticket(), self.1.clone().ex().handle().ticket()]) - } - async fn command(self) -> OrcRes { Ok((self.1, self.0)) } -} - -#[derive(Clone)] -struct Canceller { - cont: Option, - cancel: Rc>>>, -} -impl Atomic for Canceller { - type Variant = OwnedVariant; - type Data = (); -} -impl OwnedAtom for Canceller { - type Refs = Never; - async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - async fn call_ref(&self, arg: Expr) -> impl ToExpr { - match &self.cont { - Some(_) => Err(err_not_callable(&self.print_atom(&FmtCtxImpl::default()).await).await), - None => Ok(new_atom(Self { cont: Some(arg), cancel: self.cancel.clone() })), - } - } - async fn command(self) -> OrcRes { - let Some(cont) = self.cont else { - return Err(err_not_command(&self.print_atom(&FmtCtxImpl::default()).await).await); - }; - if let Some(canceller) = self.cancel.borrow_mut().take() { - canceller.send(()); - } - Ok(cont) - } -} - -#[derive(Clone)] -struct SetTimer { - delay: TimeDelta, - recurring: bool, - action: Expr, - cont: Expr, -} -impl Atomic for SetTimer { - type Variant = OwnedVariant; - type Data = (); -} -impl OwnedAtom for SetTimer { - type Refs = Never; - async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) } - async fn command(self) -> OrcRes { - let (send, recv) = oneshot::channel(); - Ok(( - new_atom(Timer { - set_at: Instant::now(), - delay: self.delay, - cancelled: Rc::new(recv), - repetition: self.recurring.then_some(1), - action: self.action, - }), - call( - self.cont, - new_atom(Canceller { cont: None, cancel: Rc::new(RefCell::new(Some(send))) }), - ), - )) - } -} - -pub fn gen_future_lib() -> Vec { - prefix("std", [comments( - [ - "This library exposes a futures executor, and tools for timing and cooperative multitasking. \ - The use of these tools is only possible in a command trampoline, i.e. a caller that always \ - defers to the command implementation of an atom.", - "Any command that correctly integrates with this library should return `std::future::yield` \ - as its final value on all codepaths, which is the (re)entry point of the trampoline. \ - Returning any other command, especially the ones in `std::exit_code` causes the program to \ - immediately exit.", - "Cancellers take a continuation, stop whatever process they are associated with from \ - proceeding, and call the continuation with information about the cancelled work.", - "|type canceller: \\T ((T -> cmd) -> cmd)|", - ], - prefix("future", [ - comments( - [ - "A command without a continuation that defers control to the queued set of commands.", - "|type: cmd|", - ], - cnst(true, "yield", new_atom(Yield)), - ), - comments( - [ - "Takes two commands and queues both to be executed one after the other.", - "|type: cmd -> cmd -> cmd|", - ], - fun(true, "spawn", async |left: ForeignAtom, right: ForeignAtom| { - new_atom(Spawn(left, right)) - }), - ), - comments( - [ - "Takes a time amount to wait, the command to perform after waiting, and a continuation, \ - and returns a command that sets a single-fire timeout. The continuation will be \ - called with a canceller, which reports true if the task has not yet run.", - "|type: Duration -> cmd -> (canceller bool -> cmd) -> cmd|", - ], - fun(true, "timeout", async |OrcDT(delay): OrcDT, action: Expr, cont: Expr| { - new_atom(SetTimer { delay, action, cont, recurring: false }) - }), - ), - comments( - [ - "Takes a time amount to wait between repetitions, the command to perform periodically, \ - and a continuation, and returns a command. The continuation will be called with a \ - canceller, which reports how many times the interval has run.", - "|type: Duration -> cmd -> (canceller Int -> cmd) -> cmd|", - ], - fun(true, "interval", async |OrcDT(delay): OrcDT, action: Expr, cont: Expr| { - new_atom(SetTimer { delay, action, cont, recurring: true }) - }), - ), - ]), - )]) -} - -fn get_scheduler() -> Scheduler { - let cted = cted(); - let std = cted.as_any().downcast_ref::().unwrap(); - let sched = std.sched.get_or_init(Scheduler::default); - sched.clone() -} - -pub struct AsyncTaskAtom {} diff --git a/orchid-std/src/std/future/mod.rs b/orchid-std/src/std/future/mod.rs deleted file mode 100644 index 853d551..0000000 --- a/orchid-std/src/std/future/mod.rs +++ /dev/null @@ -1 +0,0 @@ -// pub mod future_lib; diff --git a/orchid-std/src/std/mod.rs b/orchid-std/src/std/mod.rs index 472a627..9ef1523 100644 --- a/orchid-std/src/std/mod.rs +++ b/orchid-std/src/std/mod.rs @@ -1,6 +1,5 @@ pub mod binary; pub mod boolean; -pub mod future; pub mod number; pub mod ops; pub mod option; diff --git a/orchid.code-workspace b/orchid.code-workspace index 3100add..e3120e1 100644 --- a/orchid.code-workspace +++ b/orchid.code-workspace @@ -1,9 +1,9 @@ { - "folders": [ - { - "path": "." - } - ], + "folders": [ + { + "path": "." + } + ], "tasks": { "version": "2.0.0", "tasks": [ @@ -14,7 +14,9 @@ "cwd": "${workspaceFolder}" }, "type": "shell", - "args": ["build"], + "args": [ + "build" + ], "problemMatcher": [ "$rustc" ], @@ -54,8 +56,14 @@ "requireExactSource": true, "preLaunchTask": "Build All", "environment": [ - { "name": "ORCHID_EXTENSIONS", "value": "target/debug/orchid_std" }, - { "name": "ORCHID_DEFAULT_SYSTEMS", "value": "orchid::std;orchid::macros" }, + { + "name": "ORCHID_EXTENSIONS", + "value": "target/debug/orchid_std" + }, + { + "name": "ORCHID_DEFAULT_SYSTEMS", + "value": "orchid::std;orchid::macros" + }, ], "cwd": "${workspaceRoot}", "program": "${workspaceRoot}/target/debug/orcx.exe", @@ -70,77 +78,80 @@ ], "compounds": [] }, - "settings": { - "[markdown]": { - // markdown denotes line breaks with trailing space - "diffEditor.ignoreTrimWhitespace": false, - // Disable editor gadgets in markdown - "editor.unicodeHighlight.ambiguousCharacters": false, - "editor.unicodeHighlight.invisibleCharacters": false, - "editor.glyphMargin": false, - "editor.guides.indentation": false, - "editor.lineNumbers": "off", - "editor.quickSuggestions": { - "comments": "off", - "strings": "off", - "other": "off", - }, - "editor.rulers": [], - "editor.wordWrap": "bounded", - "editor.wordWrapColumn": 80, - // wrap lines as we go - "editor.formatOnType": true, + "settings": { + "[markdown]": { + // markdown denotes line breaks with trailing space + "diffEditor.ignoreTrimWhitespace": false, + // Disable editor gadgets in markdown + "editor.unicodeHighlight.ambiguousCharacters": false, + "editor.unicodeHighlight.invisibleCharacters": false, + "editor.glyphMargin": false, + "editor.guides.indentation": false, + "editor.lineNumbers": "off", + "editor.quickSuggestions": { + "comments": "off", + "strings": "off", + "other": "off", + }, + "editor.rulers": [], + "editor.wordWrap": "bounded", + "editor.wordWrapColumn": 80, + // wrap lines as we go + "editor.formatOnType": true, "editor.detectIndentation": false, "editor.insertSpaces": false, - }, - // Orchid is a human-made project - "chat.commandCenter.enabled": false, + }, + // Orchid is a human-made project + "chat.commandCenter.enabled": false, // use spaces for indentation for Rust for now due to a rustfmt bug - "editor.tabSize": 2, - "editor.stickyTabStops": true, - "editor.detectIndentation": false, - "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.stickyTabStops": true, + "editor.detectIndentation": false, + "editor.insertSpaces": true, // Important; for accessibility reasons, code cannot be wider than 100ch - "editor.rulers": [ 100 ], - "editor.formatOnSave": true, + "editor.rulers": [ + 100 + ], + "editor.formatOnSave": true, "files.watcherExclude": { "**/.git/objects/**": true, "**/.git/subtree-cache/**": true, "**/.hg/store/**": true, "target": true, }, - "git.confirmSync": false, - "git.enableSmartCommit": true, - "git.autofetch": true, - "rust-analyzer.assist.emitMustUse": true, - "rust-analyzer.assist.preferSelf": true, - "rust-analyzer.cargo.features": "all", - "rust-analyzer.check.command": "clippy", - "rust-analyzer.check.features": "all", - "rust-analyzer.checkOnSave": true, - "rust-analyzer.completion.fullFunctionSignatures.enable": true, - "rust-analyzer.completion.termSearch.enable": true, - "rust-analyzer.inlayHints.parameterHints.enable": false, - "rust-analyzer.inlayHints.typeHints.enable": false, - "rust-analyzer.rustfmt.extraArgs": [ - "+nightly", - ], - "rust-analyzer.showUnlinkedFileNotification": false, - "swissknife.notesEnabled": false, + "git.confirmSync": false, + "git.enableSmartCommit": true, + "git.autofetch": true, + "rust-analyzer.assist.emitMustUse": true, + "rust-analyzer.assist.preferSelf": true, + "rust-analyzer.cargo.features": "all", + "rust-analyzer.check.command": "clippy", + "rust-analyzer.check.features": "all", + "rust-analyzer.checkOnSave": true, + "rust-analyzer.completion.fullFunctionSignatures.enable": true, + "rust-analyzer.completion.termSearch.enable": true, + "rust-analyzer.inlayHints.parameterHints.enable": false, + "rust-analyzer.inlayHints.typeHints.enable": false, + "rust-analyzer.rustfmt.extraArgs": [ + "+nightly", + ], + "rust-analyzer.showUnlinkedFileNotification": false, + "swissknife.notesEnabled": false, "todo-tree.filtering.excludeGlobs": [ "**/node_modules/*/**", "orchidlang/**" + ], + "todo-tree.regex.regex": "(//|#|