bit of cleanup, few steps towards commands demo
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -1204,12 +1204,15 @@ dependencies = [
|
||||
"ctrlc",
|
||||
"futures",
|
||||
"itertools",
|
||||
"never",
|
||||
"orchid-api",
|
||||
"orchid-base",
|
||||
"orchid-extension",
|
||||
"orchid-host",
|
||||
"stacker",
|
||||
"substack",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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<R: ExprRepr, X: ExtraTok>(&self) -> TokTree<R, X> {
|
||||
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() }
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ struct WriterState {
|
||||
writer: Pin<Box<dyn AsyncWrite>>,
|
||||
}
|
||||
|
||||
pub struct OrcWriter<T: AsyncWrite + 'static>(T);
|
||||
pub struct OrcWriter<T: AsyncWrite + 'static>(pub T);
|
||||
impl<T: AsyncWrite + 'static> ToExpr for OrcWriter<T> {
|
||||
async fn to_gen(self) -> GExpr {
|
||||
new_atom(WriterAtom(Rc::new(Mutex::new(WriterState {
|
||||
@@ -61,7 +61,7 @@ impl Supports<OutputReq> for WriterAtom {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OrcReader<T: AsyncRead + 'static>(T);
|
||||
pub struct OrcReader<T: AsyncRead + 'static>(pub T);
|
||||
impl<T: AsyncRead + 'static> ToExpr for OrcReader<T> {
|
||||
async fn to_gen(self) -> GExpr {
|
||||
new_atom(ReaderAtom(Rc::new(Mutex::new(BufReader::new(Box::pin(self.0))))))
|
||||
|
||||
@@ -16,10 +16,10 @@ pub type ReqForSystem<T> = <CardForSystem<T> as SystemCard>::Req;
|
||||
/// System as defined by author
|
||||
pub trait System: Debug + 'static {
|
||||
type Ctor: SystemCtor<Instance = Self>;
|
||||
fn prelude(&self) -> impl Future<Output = Vec<Sym>>;
|
||||
fn env(&self) -> impl Future<Output = Vec<GenMember>>;
|
||||
fn lexers(&self) -> Vec<LexerObj>;
|
||||
fn parsers(&self) -> Vec<ParserObj>;
|
||||
fn prelude(&self) -> impl Future<Output = Vec<Sym>> { futures::future::ready(Vec::new()) }
|
||||
fn env(&self) -> impl Future<Output = Vec<GenMember>> { futures::future::ready(Vec::new()) }
|
||||
fn lexers(&self) -> Vec<LexerObj> { Vec::new() }
|
||||
fn parsers(&self) -> Vec<ParserObj> { Vec::new() }
|
||||
fn request<'a>(
|
||||
&self,
|
||||
hand: Box<dyn ReqHandle<'a> + 'a>,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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<AsyncTaskId>, 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<u64>,
|
||||
cancelled: Rc<Event>,
|
||||
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<Ordering> { 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<AsyncTaskId, Expr>,
|
||||
ready: VecDeque<Expr>,
|
||||
timers: BinaryHeap<Reverse<Timer>>,
|
||||
}
|
||||
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<RefCell<SchedulerState>>);
|
||||
impl Scheduler {
|
||||
pub(crate) async fn add(&self, req: &AddAsyncWork) -> <AddAsyncWork as Request>::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,
|
||||
) -> <FinishAsyncWork as Request>::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<impl IntoGExprStream> { Ok((self.1, self.0)) }
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Canceller {
|
||||
cont: Option<Expr>,
|
||||
cancel: Rc<RefCell<Option<oneshot::Sender<()>>>>,
|
||||
}
|
||||
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<impl IntoGExprStream> {
|
||||
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<impl IntoGExprStream> {
|
||||
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<GenMember> {
|
||||
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::<StdSystem>().unwrap();
|
||||
let sched = std.sched.get_or_init(Scheduler::default);
|
||||
sched.clone()
|
||||
}
|
||||
|
||||
pub struct AsyncTaskAtom {}
|
||||
@@ -1 +0,0 @@
|
||||
// pub mod future_lib;
|
||||
@@ -1,6 +1,5 @@
|
||||
pub mod binary;
|
||||
pub mod boolean;
|
||||
pub mod future;
|
||||
pub mod number;
|
||||
pub mod ops;
|
||||
pub mod option;
|
||||
|
||||
@@ -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": "(//|#|<!--|--|;|/\\*|^|^[ \\t]*(-|\\d+.))\\s*($TAGS)"
|
||||
},
|
||||
"extensions": {
|
||||
"recommendations": [
|
||||
"fill-labs.dependi",
|
||||
"gruntfuggly.todo-tree",
|
||||
"maptz.regionfolder",
|
||||
"rust-lang.rust-analyzer",
|
||||
"tamasfe.even-better-toml",
|
||||
"vadimcn.vscode-lldb",
|
||||
"yzhang.markdown-all-in-one",
|
||||
]
|
||||
},
|
||||
"extensions": {
|
||||
"recommendations": [
|
||||
"fill-labs.dependi",
|
||||
"gruntfuggly.todo-tree",
|
||||
"maptz.regionfolder",
|
||||
"rust-lang.rust-analyzer",
|
||||
"tamasfe.even-better-toml",
|
||||
"vadimcn.vscode-lldb",
|
||||
"yzhang.markdown-all-in-one",
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -13,11 +13,15 @@ clap = { version = "4.5.54", features = ["derive", "env", "cargo"] }
|
||||
ctrlc = "3.5.1"
|
||||
futures = "0.3.31"
|
||||
itertools = "0.14.0"
|
||||
never = "0.1.0"
|
||||
orchid-api = { version = "0.1.0", path = "../orchid-api" }
|
||||
orchid-base = { version = "0.1.0", path = "../orchid-base" }
|
||||
orchid-extension = { version = "0.1.0", path = "../orchid-extension" }
|
||||
orchid-host = { version = "0.1.0", path = "../orchid-host", features = [
|
||||
"tokio",
|
||||
"orchid-extension",
|
||||
] }
|
||||
stacker = "0.1.23"
|
||||
substack = "1.1.1"
|
||||
tokio = { version = "1.49.0", features = ["full"] }
|
||||
tokio-util = { version = "0.7.18", features = ["compat"] }
|
||||
|
||||
183
orcx/src/main.rs
183
orcx/src/main.rs
@@ -1,6 +1,12 @@
|
||||
use orchid_base::Logger;
|
||||
use never::Never;
|
||||
use orchid_base::{IStr, Logger, NameLike, Receipt, ReqHandle, Sym};
|
||||
use orchid_extension::{self as ox, OrcReader, OrcWriter};
|
||||
use orchid_host::cmd_system::{CmdEvent, CmdRunner};
|
||||
use orchid_host::dylib::ext_dylib;
|
||||
use orchid_host::inline::ext_inline;
|
||||
use orchid_host::tree::Root;
|
||||
use tokio::time::Instant;
|
||||
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
|
||||
pub mod parse_folder;
|
||||
mod print_mod;
|
||||
mod repl;
|
||||
@@ -22,7 +28,7 @@ use futures::{FutureExt, Stream, TryStreamExt, io};
|
||||
use itertools::Itertools;
|
||||
use orchid_base::local_interner::local_interner;
|
||||
use orchid_base::{
|
||||
FmtCtxImpl, Format, Snippet, SrcRange, Token, VPath, fmt, fmt_v, is, log, sym, take_first,
|
||||
FmtCtxImpl, 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::ctx::{Ctx, JoinHandle, Spawner};
|
||||
@@ -34,7 +40,7 @@ use orchid_host::logger::LoggerImpl;
|
||||
use orchid_host::parse::{HostParseCtxImpl, parse_item, parse_items};
|
||||
use orchid_host::parsed::{ParsTokTree, ParsedModule};
|
||||
use orchid_host::subprocess::ext_command;
|
||||
use orchid_host::system::init_systems;
|
||||
use orchid_host::system::{System, init_systems};
|
||||
use substack::Substack;
|
||||
use tokio::task::{LocalSet, spawn_local};
|
||||
|
||||
@@ -265,6 +271,90 @@ impl Spawner for SpawnerImpl {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct StdIoSystem;
|
||||
impl ox::SystemCard for StdIoSystem {
|
||||
type Ctor = Self;
|
||||
type Req = Never;
|
||||
fn atoms() -> impl IntoIterator<Item = Option<Box<dyn ox::AtomOps>>> { [] }
|
||||
}
|
||||
impl ox::SystemCtor for StdIoSystem {
|
||||
const NAME: &'static str = "orcx::stdio";
|
||||
const VERSION: f64 = 0.0;
|
||||
type Card = Self;
|
||||
type Deps = ();
|
||||
type Instance = Self;
|
||||
fn inst(&self, _: <Self::Deps as orchid_extension::DepDef>::Sat) -> Self::Instance { Self }
|
||||
}
|
||||
impl ox::System for StdIoSystem {
|
||||
type Ctor = Self;
|
||||
async fn request<'a>(
|
||||
&self,
|
||||
_: Box<dyn ReqHandle<'a> + 'a>,
|
||||
req: ox::ReqForSystem<Self>,
|
||||
) -> Receipt<'a> {
|
||||
match req {}
|
||||
}
|
||||
async fn env(&self) -> Vec<ox::tree::GenMember> {
|
||||
// TODO: this is impractical, try dialogue interface eg. prompt, choice, println
|
||||
ox::tree::module(true, "stdio", [
|
||||
ox::tree::fun(true, "get_stdin", async |cb: ox::Expr| {
|
||||
ox::cmd(async move || {
|
||||
Some(ox::gen_expr::call(cb.clone(), OrcReader(tokio::io::stdin().compat())))
|
||||
})
|
||||
}),
|
||||
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())))
|
||||
})
|
||||
}),
|
||||
ox::tree::fun(true, "get_stderr", async |cb: ox::Expr| {
|
||||
ox::cmd(async move || {
|
||||
Some(ox::gen_expr::call(cb.clone(), OrcWriter(tokio::io::stderr().compat_write())))
|
||||
})
|
||||
}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
async fn load_proj_if_set(root: &mut Root, args: &Args, ctx: &Ctx) -> Result<(), String> {
|
||||
if let Some(proj_path) = &args.proj {
|
||||
let path = proj_path.clone().into_std_path_buf();
|
||||
*root = try_with_reporter(parse_folder(root, path, sym!(src), ctx.clone()))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_const_at(
|
||||
root: &mut Root,
|
||||
ctx: &Ctx,
|
||||
systems: &[System],
|
||||
path: Sym,
|
||||
value: IStr,
|
||||
) -> Result<(), String> {
|
||||
let (name, parent) = path.split_last_seg();
|
||||
let path = Sym::new(parent.iter().cloned())
|
||||
.await
|
||||
.map_err(|_| format!("Const path must have two segments, found {path}"))?;
|
||||
let prefix_sr = SrcRange::zw(path.clone(), 0);
|
||||
let mut lexemes =
|
||||
lex(value.clone(), path.clone(), systems, ctx).await.map_err(|e| e.to_string())?;
|
||||
writeln!(log("debug"), "lexed: {}", fmt_v::<ParsTokTree>(lexemes.iter()).await.join(" ")).await;
|
||||
let parse_ctx = HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems };
|
||||
let prefix = [is("export").await, is("let").await, name, is("=").await];
|
||||
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(&parse_ctx, Substack::Bottom, vec![], snippet))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let entrypoint = ParsedModule::new(true, items);
|
||||
*root =
|
||||
with_reporter(root.add_parsed(&entrypoint, path.clone())).await.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> io::Result<ExitCode> {
|
||||
// Use a 10MB stack for single-threaded, unoptimized operation
|
||||
stacker::grow(10 * 1024 * 1024, || {
|
||||
@@ -283,10 +373,10 @@ fn main() -> io::Result<ExitCode> {
|
||||
local_set.spawn_local(async move {
|
||||
let ctx = &ctx1;
|
||||
let res = with_stash(async move {
|
||||
let extensions =
|
||||
let mut extensions =
|
||||
get_all_extensions(&args, ctx).try_collect::<Vec<Extension>>().await.unwrap();
|
||||
time_print(&args, "Extensions loaded");
|
||||
match args.command {
|
||||
match &args.command {
|
||||
Commands::Lex { file, line } => {
|
||||
let (_, systems) = init_systems(&args.system, &extensions).await.unwrap();
|
||||
let mut buf = String::new();
|
||||
@@ -323,73 +413,64 @@ fn main() -> io::Result<ExitCode> {
|
||||
);
|
||||
}
|
||||
for item in ptree {
|
||||
println!("{}", take_first(&item.print(&FmtCtxImpl::default()).await, true))
|
||||
println!("{}", fmt(&item).await)
|
||||
}
|
||||
},
|
||||
Commands::Repl => repl(&args, &extensions, ctx.clone()).await?,
|
||||
Commands::ModTree { prefix } => {
|
||||
let (mut root, _systems) = init_systems(&args.system, &extensions).await.unwrap();
|
||||
if let Some(proj_path) = args.proj {
|
||||
let path = proj_path.into_std_path_buf();
|
||||
root = try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone()))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
load_proj_if_set(&mut root, &args, ctx).await?;
|
||||
let prefix = match prefix {
|
||||
Some(pref) => VPath::parse(&pref).await,
|
||||
Some(pref) => VPath::parse(pref).await,
|
||||
None => VPath::new([]),
|
||||
};
|
||||
let root_data = root.0.read().await;
|
||||
print_mod::print_mod(&root_data.root, prefix, &root_data).await;
|
||||
},
|
||||
Commands::Eval { code } => {
|
||||
let path = sym!(usercode);
|
||||
let prefix_sr = SrcRange::zw(path.clone(), 0);
|
||||
let path = sym!(usercode::entrypoint);
|
||||
let (mut root, systems) = init_systems(&args.system, &extensions).await.unwrap();
|
||||
if let Some(proj_path) = args.proj {
|
||||
let path = proj_path.into_std_path_buf();
|
||||
root = try_with_reporter(parse_folder(&root, path, sym!(src), ctx.clone()))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
let mut lexemes = lex(is(code.trim()).await, path.clone(), &systems, ctx)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
writeln!(
|
||||
log("debug"),
|
||||
"lexed: {}",
|
||||
fmt_v::<ParsTokTree>(lexemes.iter()).await.join(" ")
|
||||
)
|
||||
.await;
|
||||
let parse_ctx =
|
||||
HostParseCtxImpl { ctx: ctx.clone(), src: path.clone(), systems: &systems[..] };
|
||||
let prefix =
|
||||
[is("export").await, is("let").await, is("entrypoint").await, is("=").await];
|
||||
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(&parse_ctx, Substack::Bottom, vec![], snippet))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let entrypoint = ParsedModule::new(true, items);
|
||||
let root = with_reporter(root.add_parsed(&entrypoint, path.clone()))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
let expr = ExprKind::Const(sym!(usercode::entrypoint)).at(prefix_sr.pos());
|
||||
load_proj_if_set(&mut root, &args, ctx).await?;
|
||||
add_const_at(&mut root, ctx, &systems[..], path.clone(), is(code).await).await?;
|
||||
let expr = ExprKind::Const(path.clone()).at(SrcRange::zw(path.clone(), 0).pos());
|
||||
let mut xctx = ExecCtx::new(root.clone(), expr).await;
|
||||
if !args.no_gas {
|
||||
xctx.set_gas(Some(args.gas));
|
||||
}
|
||||
match xctx.execute().await {
|
||||
ExecResult::Value(val, _) => {
|
||||
println!("{}", take_first(&val.print(&FmtCtxImpl::default()).await, false))
|
||||
},
|
||||
ExecResult::Value(val, _) => println!("{}", fmt(&val).await),
|
||||
ExecResult::Err(e, _) => println!("error: {e}"),
|
||||
ExecResult::Gas(_) => println!("Ran out of gas!"),
|
||||
ExecResult::Gas(_) => println!("Exceeded gas limit of {}", args.gas),
|
||||
}
|
||||
},
|
||||
Commands::Exec { main: _ } => {
|
||||
todo!("Integration of the command system is in-dev")
|
||||
Commands::Exec { main } => {
|
||||
let path = sym!(usercode::entrypoint);
|
||||
let (mut root, mut systems) = init_systems(&args.system, &extensions).await.unwrap();
|
||||
let io_ext_builder = ox::ExtensionBuilder::new("orcx::stdio").system(StdIoSystem);
|
||||
let io_ext_init = ext_inline(io_ext_builder, ctx.clone()).await;
|
||||
let io_ext =
|
||||
Extension::new(io_ext_init, ctx.clone()).await.map_err(|e| e.to_string())?;
|
||||
let io_ctor = (io_ext.system_ctors().find(|ctor| ctor.name() == "orchid::cmd"))
|
||||
.expect("Missing command system ctor");
|
||||
let (io_root, io_system) = io_ctor.run(vec![]).await;
|
||||
root = root.merge(&io_root).await.expect("Failed to merge stdio root into tree");
|
||||
systems.push(io_system);
|
||||
extensions.push(io_ext);
|
||||
load_proj_if_set(&mut root, &args, ctx).await?;
|
||||
add_const_at(&mut root, ctx, &systems[..], path.clone(), is(main).await).await?;
|
||||
let expr = ExprKind::Const(path.clone()).at(SrcRange::zw(path.clone(), 0).pos());
|
||||
let mut crun = CmdRunner::new(root, ctx.clone(), [expr]).await;
|
||||
if !args.no_gas {
|
||||
crun.set_gas(args.gas);
|
||||
}
|
||||
match crun.execute().await {
|
||||
CmdEvent::Exit | CmdEvent::Settled => (),
|
||||
CmdEvent::Err(e) => println!("error: {e}"),
|
||||
CmdEvent::Gas => println!("Exceeded gas limit of {}", args.gas),
|
||||
CmdEvent::NonCommand(val) => {
|
||||
println!("Non-command value: {}", fmt(&val).await)
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user