forked from Orchid/orchid
Most files suffered major changes
- Less ambiguous syntax - Better parser (Chumsky only does tokenization now) - Tidy(|ier) error handling - Facade for simplified embedding - External code grouped in (fairly) self-contained Systems - Dynamic action dispatch - Many STL additions
This commit is contained in:
37
src/systems/assertion_error.rs
Normal file
37
src/systems/assertion_error.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::fmt::Display;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::foreign::ExternError;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
|
||||
/// Some expectation (usually about the argument types of a function) did not
|
||||
/// hold.
|
||||
#[derive(Clone)]
|
||||
pub struct AssertionError {
|
||||
value: ExprInst,
|
||||
assertion: &'static str,
|
||||
}
|
||||
|
||||
impl AssertionError {
|
||||
/// Construct, upcast and wrap in a Result that never succeeds for easy
|
||||
/// short-circuiting
|
||||
pub fn fail<T>(
|
||||
value: ExprInst,
|
||||
assertion: &'static str,
|
||||
) -> Result<T, Rc<dyn ExternError>> {
|
||||
return Err(Self { value, assertion }.into_extern());
|
||||
}
|
||||
|
||||
/// Construct and upcast to [ExternError]
|
||||
pub fn ext(value: ExprInst, assertion: &'static str) -> Rc<dyn ExternError> {
|
||||
return Self { value, assertion }.into_extern();
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AssertionError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Error: {:?} is not {}", self.value, self.assertion)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternError for AssertionError {}
|
||||
5
src/systems/asynch/mod.rs
Normal file
5
src/systems/asynch/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod system;
|
||||
mod types;
|
||||
|
||||
pub use system::{AsynchConfig, InfiniteBlock};
|
||||
pub use types::{Asynch, MessagePort};
|
||||
183
src/systems/asynch/system.rs
Normal file
183
src/systems/asynch/system.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use std::any::{type_name, Any, TypeId};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::time::Duration;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::types::MessagePort;
|
||||
use super::Asynch;
|
||||
use crate::facade::{IntoSystem, System};
|
||||
use crate::foreign::cps_box::{init_cps, CPSBox};
|
||||
use crate::foreign::ExternError;
|
||||
use crate::interpreted::ExprInst;
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::systems::codegen::call;
|
||||
use crate::systems::stl::Boolean;
|
||||
use crate::utils::{unwrap_or, PollEvent, Poller};
|
||||
use crate::{atomic_inert, define_fn, ConstTree, Interner};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Timer {
|
||||
recurring: Boolean,
|
||||
duration: NotNan<f64>,
|
||||
}
|
||||
define_fn! {expr=x in
|
||||
SetTimer {
|
||||
recurring: Boolean,
|
||||
duration: NotNan<f64>
|
||||
} => Ok(init_cps(2, Timer{
|
||||
recurring: *recurring,
|
||||
duration: *duration
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CancelTimer(Rc<dyn Fn()>);
|
||||
impl Debug for CancelTimer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "opaque cancel operation")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Yield;
|
||||
atomic_inert!(Yield, "a yield command");
|
||||
|
||||
/// Error indicating a yield command when all event producers and timers had
|
||||
/// exited
|
||||
pub struct InfiniteBlock;
|
||||
impl ExternError for InfiniteBlock {}
|
||||
impl Display for InfiniteBlock {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
static MSG: &str = "User code yielded, but there are no timers or event \
|
||||
producers to wake it up in the future";
|
||||
write!(f, "{}", MSG)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessagePort for Sender<Box<dyn Any + Send>> {
|
||||
fn send<T: Send + 'static>(&mut self, message: T) {
|
||||
let _ = Self::send(self, Box::new(message));
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> MessagePort for F
|
||||
where
|
||||
F: FnMut(Box<dyn Any + Send>) + Send + Clone + 'static,
|
||||
{
|
||||
fn send<T: Send + 'static>(&mut self, message: T) {
|
||||
self(Box::new(message))
|
||||
}
|
||||
}
|
||||
|
||||
type AnyHandler<'a> = Box<dyn FnMut(Box<dyn Any>) -> Option<ExprInst> + 'a>;
|
||||
|
||||
/// Datastructures the asynch system will eventually be constructed from
|
||||
pub struct AsynchConfig<'a> {
|
||||
poller: Poller<Box<dyn Any + Send>, ExprInst, ExprInst>,
|
||||
sender: Sender<Box<dyn Any + Send>>,
|
||||
handlers: HashMap<TypeId, AnyHandler<'a>>,
|
||||
}
|
||||
impl<'a> AsynchConfig<'a> {
|
||||
/// Create a new async event loop that allows registering handlers and taking
|
||||
/// references to the port before it's converted into a [System]
|
||||
pub fn new() -> Self {
|
||||
let (sender, poller) = Poller::new();
|
||||
Self { poller, sender, handlers: HashMap::new() }
|
||||
}
|
||||
}
|
||||
impl<'a> Asynch for AsynchConfig<'a> {
|
||||
type Port = Sender<Box<dyn Any + Send>>;
|
||||
|
||||
fn register<T: 'static>(
|
||||
&mut self,
|
||||
mut f: impl FnMut(Box<T>) -> Option<ExprInst> + 'a,
|
||||
) {
|
||||
let cb = move |a: Box<dyn Any>| f(a.downcast().expect("keyed by TypeId"));
|
||||
let prev = self.handlers.insert(TypeId::of::<T>(), Box::new(cb));
|
||||
assert!(
|
||||
prev.is_none(),
|
||||
"Duplicate handlers for async event {}",
|
||||
type_name::<T>()
|
||||
)
|
||||
}
|
||||
|
||||
fn get_port(&self) -> Self::Port {
|
||||
self.sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Default for AsynchConfig<'a> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoSystem<'a> for AsynchConfig<'a> {
|
||||
fn into_system(self, i: &Interner) -> System<'a> {
|
||||
let Self { mut handlers, poller, .. } = self;
|
||||
let mut handler_table = HandlerTable::new();
|
||||
let polly = Rc::new(RefCell::new(poller));
|
||||
handler_table.register({
|
||||
let polly = polly.clone();
|
||||
move |t: &CPSBox<Timer>| {
|
||||
let mut polly = polly.borrow_mut();
|
||||
let (timeout, action, cont) = t.unpack2();
|
||||
let duration = Duration::from_secs_f64(*timeout.duration);
|
||||
let cancel_timer = if timeout.recurring.0 {
|
||||
CancelTimer(Rc::new(polly.set_interval(duration, action.clone())))
|
||||
} else {
|
||||
CancelTimer(Rc::new(polly.set_timeout(duration, action.clone())))
|
||||
};
|
||||
Ok(call(cont.clone(), [init_cps(1, cancel_timer).wrap()]).wrap())
|
||||
}
|
||||
});
|
||||
handler_table.register(move |t: &CPSBox<CancelTimer>| {
|
||||
let (command, cont) = t.unpack1();
|
||||
command.0.as_ref()();
|
||||
Ok(cont.clone())
|
||||
});
|
||||
handler_table.register({
|
||||
let polly = polly.clone();
|
||||
move |_: &Yield| {
|
||||
let mut polly = polly.borrow_mut();
|
||||
loop {
|
||||
let next = unwrap_or!(polly.run();
|
||||
return Err(InfiniteBlock.into_extern())
|
||||
);
|
||||
match next {
|
||||
PollEvent::Once(expr) => return Ok(expr),
|
||||
PollEvent::Recurring(expr) => return Ok(expr),
|
||||
PollEvent::Event(ev) => {
|
||||
let handler = (handlers.get_mut(&ev.as_ref().type_id()))
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Unhandled messgae type: {:?}", ev.type_id())
|
||||
});
|
||||
if let Some(expr) = handler(ev) {
|
||||
return Ok(expr);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
System {
|
||||
name: vec!["system".to_string(), "asynch".to_string()],
|
||||
constants: ConstTree::namespace(
|
||||
[i.i("system"), i.i("async")],
|
||||
ConstTree::tree([
|
||||
(i.i("set_timer"), ConstTree::xfn(SetTimer)),
|
||||
(i.i("yield"), ConstTree::atom(Yield)),
|
||||
]),
|
||||
)
|
||||
.unwrap_tree(),
|
||||
code: HashMap::new(),
|
||||
prelude: Vec::new(),
|
||||
handlers: handler_table,
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/systems/asynch/types.rs
Normal file
30
src/systems/asynch/types.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use crate::interpreted::ExprInst;
|
||||
|
||||
/// A thread-safe handle that can be used to send events of any type
|
||||
pub trait MessagePort: Send + Clone + 'static {
|
||||
/// Send an event. Any type is accepted, handlers are dispatched by type ID
|
||||
fn send<T: Send + 'static>(&mut self, message: T);
|
||||
}
|
||||
|
||||
pub trait Asynch {
|
||||
/// A thread-safe handle that can be used to push events into the dispatcher
|
||||
type Port: MessagePort;
|
||||
|
||||
/// Register a function that will be called synchronously when an event of the
|
||||
/// accepted type is dispatched. Only one handler may be specified for each
|
||||
/// event type. The handler may choose to process the event autonomously, or
|
||||
/// return an Orchid thunk for the interpreter to execute.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// When the function is called with an argument type it was previously called
|
||||
/// with
|
||||
fn register<T: 'static>(
|
||||
&mut self,
|
||||
f: impl FnMut(Box<T>) -> Option<ExprInst> + 'static,
|
||||
);
|
||||
|
||||
/// Return a handle that can be passed to worker threads and used to push
|
||||
/// events onto the dispatcher
|
||||
fn get_port(&self) -> Self::Port;
|
||||
}
|
||||
118
src/systems/cast_exprinst.rs
Normal file
118
src/systems/cast_exprinst.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
//! Utility functions that operate on literals. Because of the parallel locked
|
||||
//! nature of [ExprInst], returning a reference to [Literal] is not possible.
|
||||
use std::rc::Rc;
|
||||
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::assertion_error::AssertionError;
|
||||
use crate::foreign::{Atomic, ExternError};
|
||||
use crate::interpreted::Clause;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::representations::Literal;
|
||||
use crate::Primitive;
|
||||
|
||||
/// Tries to cast the [ExprInst] as a [Literal], calls the provided function on
|
||||
/// it if successful. Returns a generic [AssertionError] if not.
|
||||
pub fn with_lit<T>(
|
||||
x: &ExprInst,
|
||||
predicate: impl FnOnce(&Literal) -> Result<T, Rc<dyn ExternError>>,
|
||||
) -> Result<T, Rc<dyn ExternError>> {
|
||||
x.with_literal(predicate)
|
||||
.map_err(|_| AssertionError::ext(x.clone(), "a literal value"))
|
||||
.and_then(|r| r)
|
||||
}
|
||||
|
||||
/// Like [with_lit] but also unwraps [Literal::Str]
|
||||
pub fn with_str<T>(
|
||||
x: &ExprInst,
|
||||
predicate: impl FnOnce(&String) -> Result<T, Rc<dyn ExternError>>,
|
||||
) -> Result<T, Rc<dyn ExternError>> {
|
||||
with_lit(x, |l| {
|
||||
if let Literal::Str(s) = l {
|
||||
predicate(s)
|
||||
} else {
|
||||
AssertionError::fail(x.clone(), "a string")?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Like [with_lit] but also unwraps [Literal::Uint]
|
||||
pub fn with_uint<T>(
|
||||
x: &ExprInst,
|
||||
predicate: impl FnOnce(u64) -> Result<T, Rc<dyn ExternError>>,
|
||||
) -> Result<T, Rc<dyn ExternError>> {
|
||||
with_lit(x, |l| {
|
||||
if let Literal::Uint(u) = l {
|
||||
predicate(*u)
|
||||
} else {
|
||||
AssertionError::fail(x.clone(), "an uint")?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Like [with_lit] but also unwraps [Literal::Num]
|
||||
pub fn with_num<T>(
|
||||
x: &ExprInst,
|
||||
predicate: impl FnOnce(NotNan<f64>) -> Result<T, Rc<dyn ExternError>>,
|
||||
) -> Result<T, Rc<dyn ExternError>> {
|
||||
with_lit(x, |l| {
|
||||
if let Literal::Num(n) = l {
|
||||
predicate(*n)
|
||||
} else {
|
||||
AssertionError::fail(x.clone(), "a float")?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Tries to cast the [ExprInst] into the specified atom type. Throws an
|
||||
/// assertion error if unsuccessful, or calls the provided function on the
|
||||
/// extracted atomic type.
|
||||
pub fn with_atom<T: Atomic, U>(
|
||||
x: &ExprInst,
|
||||
inexact_typename: &'static str,
|
||||
predicate: impl FnOnce(&T) -> Result<U, Rc<dyn ExternError>>,
|
||||
) -> Result<U, Rc<dyn ExternError>> {
|
||||
x.inspect(|c| {
|
||||
if let Clause::P(Primitive::Atom(a)) = c {
|
||||
a.try_cast()
|
||||
.map(predicate)
|
||||
.unwrap_or_else(|| AssertionError::fail(x.clone(), inexact_typename))
|
||||
} else {
|
||||
AssertionError::fail(x.clone(), "an atom")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ######## Automatically ########
|
||||
|
||||
impl TryFrom<&ExprInst> for Literal {
|
||||
type Error = Rc<dyn ExternError>;
|
||||
|
||||
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||
with_lit(value, |l| Ok(l.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ExprInst> for String {
|
||||
type Error = Rc<dyn ExternError>;
|
||||
|
||||
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||
with_str(value, |s| Ok(s.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ExprInst> for u64 {
|
||||
type Error = Rc<dyn ExternError>;
|
||||
|
||||
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||
with_uint(value, Ok)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ExprInst> for NotNan<f64> {
|
||||
type Error = Rc<dyn ExternError>;
|
||||
|
||||
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||
with_num(value, Ok)
|
||||
}
|
||||
}
|
||||
57
src/systems/codegen.rs
Normal file
57
src/systems/codegen.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
//! Utilities for generating Orchid code in Rust
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::interpreted::{Clause, ExprInst};
|
||||
use crate::utils::unwrap_or;
|
||||
use crate::{PathSet, Side};
|
||||
|
||||
/// Convert a rust Option into an Orchid Option
|
||||
pub fn orchid_opt(x: Option<ExprInst>) -> Clause {
|
||||
if let Some(x) = x { some(x) } else { none() }
|
||||
}
|
||||
|
||||
/// Constructs an instance of the orchid value Some wrapping the given
|
||||
/// [ExprInst].
|
||||
///
|
||||
/// Takes two expressions and calls the second with the given data
|
||||
fn some(x: ExprInst) -> Clause {
|
||||
Clause::Lambda {
|
||||
args: None,
|
||||
body: Clause::Lambda {
|
||||
args: Some(PathSet { steps: Rc::new(vec![Side::Left]), next: None }),
|
||||
body: Clause::Apply { f: Clause::LambdaArg.wrap(), x }.wrap(),
|
||||
}
|
||||
.wrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs an instance of the orchid value None
|
||||
///
|
||||
/// Takes two expressions and returns the first
|
||||
fn none() -> Clause {
|
||||
Clause::Lambda {
|
||||
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
|
||||
body: Clause::Lambda { args: None, body: Clause::LambdaArg.wrap() }.wrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Define a clause that can be called with a callback and passes the provided
|
||||
/// values to the callback in order.
|
||||
pub fn tuple(data: Vec<ExprInst>) -> Clause {
|
||||
Clause::Lambda {
|
||||
args: Some(PathSet {
|
||||
next: None,
|
||||
steps: Rc::new(data.iter().map(|_| Side::Left).collect()),
|
||||
}),
|
||||
body: (data.into_iter())
|
||||
.fold(Clause::LambdaArg.wrap(), |f, x| Clause::Apply { f, x }.wrap()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a function call with the specified arugment array.
|
||||
pub fn call(f: ExprInst, args: impl IntoIterator<Item = ExprInst>) -> Clause {
|
||||
let mut it = args.into_iter();
|
||||
let x = unwrap_or!(it.by_ref().next(); return f.inspect(Clause::clone));
|
||||
it.fold(Clause::Apply { f, x }, |acc, x| Clause::Apply { f: acc.wrap(), x })
|
||||
}
|
||||
103
src/systems/io/bindings.rs
Normal file
103
src/systems/io/bindings.rs
Normal file
@@ -0,0 +1,103 @@
|
||||
use super::flow::IOCmdHandlePack;
|
||||
use super::instances::{
|
||||
BRead, ReadCmd, SRead, SinkHandle, SourceHandle, WriteCmd,
|
||||
};
|
||||
use crate::foreign::cps_box::init_cps;
|
||||
use crate::foreign::{Atom, Atomic};
|
||||
use crate::systems::stl::Binary;
|
||||
use crate::systems::RuntimeError;
|
||||
use crate::{ast, define_fn, ConstTree, Interner, Primitive};
|
||||
|
||||
define_fn! {
|
||||
ReadString = |x| Ok(init_cps(3, IOCmdHandlePack{
|
||||
cmd: ReadCmd::RStr(SRead::All),
|
||||
handle: x.try_into()?
|
||||
}))
|
||||
}
|
||||
define_fn! {
|
||||
ReadLine = |x| Ok(init_cps(3, IOCmdHandlePack{
|
||||
cmd: ReadCmd::RStr(SRead::Line),
|
||||
handle: x.try_into()?
|
||||
}))
|
||||
}
|
||||
define_fn! {
|
||||
ReadBin = |x| Ok(init_cps(3, IOCmdHandlePack{
|
||||
cmd: ReadCmd::RBytes(BRead::All),
|
||||
handle: x.try_into()?
|
||||
}))
|
||||
}
|
||||
define_fn! {
|
||||
ReadBytes {
|
||||
stream: SourceHandle,
|
||||
n: u64
|
||||
} => Ok(init_cps(3, IOCmdHandlePack{
|
||||
cmd: ReadCmd::RBytes(BRead::N((*n).try_into().unwrap())),
|
||||
handle: *stream
|
||||
}))
|
||||
}
|
||||
define_fn! {
|
||||
ReadUntil {
|
||||
stream: SourceHandle,
|
||||
pattern: u64
|
||||
} => {
|
||||
let delim = (*pattern).try_into().map_err(|_| RuntimeError::ext(
|
||||
"greater than 255".to_string(),
|
||||
"converting number to byte"
|
||||
))?;
|
||||
Ok(init_cps(3, IOCmdHandlePack{
|
||||
cmd: ReadCmd::RBytes(BRead::Until(delim)),
|
||||
handle: *stream
|
||||
}))
|
||||
}
|
||||
}
|
||||
define_fn! {
|
||||
WriteStr {
|
||||
stream: SinkHandle,
|
||||
string: String
|
||||
} => Ok(init_cps(3, IOCmdHandlePack {
|
||||
cmd: WriteCmd::WStr(string.clone()),
|
||||
handle: *stream,
|
||||
}))
|
||||
}
|
||||
define_fn! {
|
||||
WriteBin {
|
||||
stream: SinkHandle,
|
||||
bytes: Binary
|
||||
} => Ok(init_cps(3, IOCmdHandlePack {
|
||||
cmd: WriteCmd::WBytes(bytes.clone()),
|
||||
handle: *stream
|
||||
}))
|
||||
}
|
||||
define_fn! {
|
||||
Flush = |x| Ok(init_cps(3, IOCmdHandlePack {
|
||||
cmd: WriteCmd::Flush,
|
||||
handle: x.try_into()?
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn io_bindings(
|
||||
i: &Interner,
|
||||
std_streams: impl IntoIterator<Item = (&'static str, Box<dyn Atomic>)>,
|
||||
) -> ConstTree {
|
||||
ConstTree::namespace(
|
||||
[i.i("system"), i.i("io")],
|
||||
ConstTree::tree([
|
||||
(i.i("read_string"), ConstTree::xfn(ReadString)),
|
||||
(i.i("read_line"), ConstTree::xfn(ReadLine)),
|
||||
(i.i("read_bin"), ConstTree::xfn(ReadBin)),
|
||||
(i.i("read_n_bytes"), ConstTree::xfn(ReadBytes)),
|
||||
(i.i("read_until"), ConstTree::xfn(ReadUntil)),
|
||||
(i.i("write_str"), ConstTree::xfn(WriteStr)),
|
||||
(i.i("write_bin"), ConstTree::xfn(WriteBin)),
|
||||
(i.i("flush"), ConstTree::xfn(Flush)),
|
||||
]) + ConstTree::Tree(
|
||||
std_streams
|
||||
.into_iter()
|
||||
.map(|(n, at)| {
|
||||
let expr = ast::Clause::P(Primitive::Atom(Atom(at))).into_expr();
|
||||
(i.i(n), ConstTree::Const(expr))
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
)
|
||||
}
|
||||
154
src/systems/io/facade.rs
Normal file
154
src/systems/io/facade.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
#![allow(non_upper_case_globals)] // RustEmbed is sloppy
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use rust_embed::RustEmbed;
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::bindings::io_bindings;
|
||||
use super::flow::{IOCmdHandlePack, IOManager, NoActiveStream};
|
||||
use super::instances::{
|
||||
ReadCmd, ReadManager, Sink, SinkHandle, Source, SourceHandle, WriteCmd,
|
||||
WriteManager,
|
||||
};
|
||||
use crate::facade::{IntoSystem, System};
|
||||
use crate::foreign::cps_box::CPSBox;
|
||||
use crate::foreign::{Atomic, ExternError};
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::pipeline::file_loader::embed_to_map;
|
||||
use crate::sourcefile::{FileEntry, Import};
|
||||
use crate::systems::asynch::{Asynch, MessagePort};
|
||||
use crate::Interner;
|
||||
|
||||
trait_set! {
|
||||
pub trait StreamTable = IntoIterator<Item = (&'static str, IOStream)>
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/systems/io"]
|
||||
#[prefix = "system/"]
|
||||
#[include = "*.orc"]
|
||||
struct IOEmbed;
|
||||
|
||||
/// A registry that stores IO streams and executes blocking operations on them
|
||||
/// in a distinct thread pool
|
||||
pub struct IOSystem<P: MessagePort, ST: StreamTable> {
|
||||
read_system: Rc<RefCell<ReadManager<P>>>,
|
||||
write_system: Rc<RefCell<WriteManager<P>>>,
|
||||
global_streams: ST,
|
||||
}
|
||||
impl<P: MessagePort, ST: StreamTable> IOSystem<P, ST> {
|
||||
fn new(
|
||||
mut get_port: impl FnMut() -> P,
|
||||
on_sink_close: Option<Box<dyn FnMut(Sink)>>,
|
||||
on_source_close: Option<Box<dyn FnMut(Source)>>,
|
||||
global_streams: ST,
|
||||
) -> Self {
|
||||
Self {
|
||||
read_system: Rc::new(RefCell::new(IOManager::new(
|
||||
get_port(),
|
||||
on_source_close,
|
||||
))),
|
||||
write_system: Rc::new(RefCell::new(IOManager::new(
|
||||
get_port(),
|
||||
on_sink_close,
|
||||
))),
|
||||
global_streams,
|
||||
}
|
||||
}
|
||||
/// Register a new source so that it can be used with IO commands
|
||||
pub fn add_source(&self, source: Source) -> SourceHandle {
|
||||
self.read_system.borrow_mut().add_stream(source)
|
||||
}
|
||||
/// Register a new sink so that it can be used with IO operations
|
||||
pub fn add_sink(&self, sink: Sink) -> SinkHandle {
|
||||
self.write_system.borrow_mut().add_stream(sink)
|
||||
}
|
||||
/// Schedule a source to be closed when all currently enqueued IO operations
|
||||
/// finish.
|
||||
pub fn close_source(
|
||||
&self,
|
||||
handle: SourceHandle,
|
||||
) -> Result<(), NoActiveStream> {
|
||||
self.read_system.borrow_mut().close_stream(handle)
|
||||
}
|
||||
/// Schedule a sink to be closed when all current IO operations finish.
|
||||
pub fn close_sink(&self, handle: SinkHandle) -> Result<(), NoActiveStream> {
|
||||
self.write_system.borrow_mut().close_stream(handle)
|
||||
}
|
||||
}
|
||||
|
||||
/// A shared type for sinks and sources
|
||||
pub enum IOStream {
|
||||
/// A Source, aka. a BufReader
|
||||
Source(Source),
|
||||
/// A Sink, aka. a Writer
|
||||
Sink(Sink),
|
||||
}
|
||||
|
||||
/// Construct an [IOSystem]. An event loop ([AsynchConfig]) is required to
|
||||
/// sequence IO events on the interpreter thread.
|
||||
///
|
||||
/// This is a distinct function because [IOSystem]
|
||||
/// takes a generic parameter which is initialized from an existential in the
|
||||
/// [AsynchConfig].
|
||||
pub fn io_system(
|
||||
asynch: &'_ mut impl Asynch,
|
||||
on_sink_close: Option<Box<dyn FnMut(Sink)>>,
|
||||
on_source_close: Option<Box<dyn FnMut(Source)>>,
|
||||
std_streams: impl IntoIterator<Item = (&'static str, IOStream)>,
|
||||
) -> IOSystem<impl MessagePort, impl StreamTable> {
|
||||
let this = IOSystem::new(
|
||||
|| asynch.get_port(),
|
||||
on_sink_close,
|
||||
on_source_close,
|
||||
std_streams,
|
||||
);
|
||||
let (r, w) = (this.read_system.clone(), this.write_system.clone());
|
||||
asynch.register(move |event| r.borrow_mut().dispatch(*event));
|
||||
asynch.register(move |event| w.borrow_mut().dispatch(*event));
|
||||
this
|
||||
}
|
||||
|
||||
impl<'a, P: MessagePort, ST: StreamTable + 'a> IntoSystem<'a>
|
||||
for IOSystem<P, ST>
|
||||
{
|
||||
fn into_system(self, i: &Interner) -> System<'a> {
|
||||
let (r, w) = (self.read_system.clone(), self.write_system.clone());
|
||||
let mut handlers = HandlerTable::new();
|
||||
handlers.register(move |cps: &CPSBox<IOCmdHandlePack<ReadCmd>>| {
|
||||
let (IOCmdHandlePack { cmd, handle }, succ, fail, tail) = cps.unpack3();
|
||||
(r.borrow_mut())
|
||||
.command(*handle, *cmd, (succ.clone(), fail.clone()))
|
||||
.map_err(|e| e.into_extern())?;
|
||||
Ok(tail.clone())
|
||||
});
|
||||
handlers.register(move |cps: &CPSBox<IOCmdHandlePack<WriteCmd>>| {
|
||||
let (IOCmdHandlePack { cmd, handle }, succ, fail, tail) = cps.unpack3();
|
||||
(w.borrow_mut())
|
||||
.command(*handle, cmd.clone(), (succ.clone(), fail.clone()))
|
||||
.map_err(|e| e.into_extern())?;
|
||||
Ok(tail.clone())
|
||||
});
|
||||
let streams = self.global_streams.into_iter().map(|(n, stream)| {
|
||||
let handle = match stream {
|
||||
IOStream::Sink(sink) =>
|
||||
Box::new(self.write_system.borrow_mut().add_stream(sink))
|
||||
as Box<dyn Atomic>,
|
||||
IOStream::Source(source) =>
|
||||
Box::new(self.read_system.borrow_mut().add_stream(source)),
|
||||
};
|
||||
(n, handle)
|
||||
});
|
||||
System {
|
||||
name: vec!["system".to_string(), "io".to_string()],
|
||||
constants: io_bindings(i, streams).unwrap_tree(),
|
||||
code: embed_to_map::<IOEmbed>(".orc", i),
|
||||
prelude: vec![FileEntry::Import(vec![Import {
|
||||
path: i.i(&vec![i.i("system"), i.i("io"), i.i("prelude")]),
|
||||
name: None,
|
||||
}])],
|
||||
handlers,
|
||||
}
|
||||
}
|
||||
}
|
||||
179
src/systems/io/flow.rs
Normal file
179
src/systems/io/flow.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Display;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use crate::foreign::ExternError;
|
||||
use crate::systems::asynch::MessagePort;
|
||||
use crate::utils::{take_with_output, Task};
|
||||
use crate::ThreadPool;
|
||||
|
||||
pub trait StreamHandle: Clone + Send {
|
||||
fn new(id: usize) -> Self;
|
||||
fn id(&self) -> usize;
|
||||
}
|
||||
|
||||
pub trait IOHandler<Cmd: IOCmd> {
|
||||
type Product;
|
||||
|
||||
fn handle(self, result: Cmd::Result) -> Self::Product;
|
||||
}
|
||||
|
||||
pub trait IOResult: Send {
|
||||
type Handler;
|
||||
type HandlerProduct;
|
||||
|
||||
fn handle(self, handler: Self::Handler) -> Self::HandlerProduct;
|
||||
}
|
||||
|
||||
pub struct IOEvent<Cmd: IOCmd> {
|
||||
pub result: Cmd::Result,
|
||||
pub stream: Cmd::Stream,
|
||||
pub handle: Cmd::Handle,
|
||||
}
|
||||
|
||||
pub trait IOCmd: Send {
|
||||
type Stream: Send;
|
||||
type Result: Send;
|
||||
type Handle: StreamHandle;
|
||||
|
||||
fn execute(self, stream: &mut Self::Stream) -> Self::Result;
|
||||
}
|
||||
|
||||
pub struct IOTask<P: MessagePort, Cmd: IOCmd> {
|
||||
pub cmd: Cmd,
|
||||
pub stream: Cmd::Stream,
|
||||
pub handle: Cmd::Handle,
|
||||
pub port: P,
|
||||
}
|
||||
|
||||
impl<P: MessagePort, Cmd: IOCmd + 'static> Task for IOTask<P, Cmd> {
|
||||
fn run(self) {
|
||||
let Self { cmd, handle, mut port, mut stream } = self;
|
||||
let result = cmd.execute(&mut stream);
|
||||
port.send(IOEvent::<Cmd> { handle, result, stream })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IOCmdHandlePack<Cmd: IOCmd> {
|
||||
pub cmd: Cmd,
|
||||
pub handle: Cmd::Handle,
|
||||
}
|
||||
|
||||
enum StreamState<Cmd: IOCmd, H: IOHandler<Cmd>> {
|
||||
Free(Cmd::Stream),
|
||||
Busy { handler: H, queue: VecDeque<(Cmd, H)>, closing: bool },
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct NoActiveStream(usize);
|
||||
impl ExternError for NoActiveStream {}
|
||||
impl Display for NoActiveStream {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "The stream {} had already been closed", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IOManager<P: MessagePort, Cmd: IOCmd + 'static, H: IOHandler<Cmd>> {
|
||||
next_id: usize,
|
||||
streams: HashMap<usize, StreamState<Cmd, H>>,
|
||||
on_close: Option<Box<dyn FnMut(Cmd::Stream)>>,
|
||||
thread_pool: ThreadPool<IOTask<P, Cmd>>,
|
||||
port: P,
|
||||
}
|
||||
|
||||
impl<P: MessagePort, Cmd: IOCmd, H: IOHandler<Cmd>> IOManager<P, Cmd, H> {
|
||||
pub fn new(port: P, on_close: Option<Box<dyn FnMut(Cmd::Stream)>>) -> Self {
|
||||
Self {
|
||||
next_id: 0,
|
||||
streams: HashMap::new(),
|
||||
thread_pool: ThreadPool::new(),
|
||||
on_close,
|
||||
port,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_stream(&mut self, stream: Cmd::Stream) -> Cmd::Handle {
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
self.streams.insert(id, StreamState::Free(stream));
|
||||
Cmd::Handle::new(id)
|
||||
}
|
||||
|
||||
fn dispose_stream(&mut self, stream: Cmd::Stream) {
|
||||
match &mut self.on_close {
|
||||
Some(f) => f(stream),
|
||||
None => drop(stream),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close_stream(
|
||||
&mut self,
|
||||
handle: Cmd::Handle,
|
||||
) -> Result<(), NoActiveStream> {
|
||||
let state =
|
||||
(self.streams.remove(&handle.id())).ok_or(NoActiveStream(handle.id()))?;
|
||||
match state {
|
||||
StreamState::Free(stream) => self.dispose_stream(stream),
|
||||
StreamState::Busy { handler, queue, closing } => {
|
||||
let new_state = StreamState::Busy { handler, queue, closing: true };
|
||||
self.streams.insert(handle.id(), new_state);
|
||||
if closing {
|
||||
return Err(NoActiveStream(handle.id()));
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn command(
|
||||
&mut self,
|
||||
handle: Cmd::Handle,
|
||||
cmd: Cmd,
|
||||
new_handler: H,
|
||||
) -> Result<(), NoActiveStream> {
|
||||
let state_mut = (self.streams.get_mut(&handle.id()))
|
||||
.ok_or(NoActiveStream(handle.id()))?;
|
||||
take_with_output(state_mut, |state| match state {
|
||||
StreamState::Busy { closing: true, .. } =>
|
||||
(state, Err(NoActiveStream(handle.id()))),
|
||||
StreamState::Busy { handler, mut queue, closing: false } => {
|
||||
queue.push_back((cmd, new_handler));
|
||||
(StreamState::Busy { handler, queue, closing: false }, Ok(()))
|
||||
},
|
||||
StreamState::Free(stream) => {
|
||||
let task = IOTask { cmd, stream, handle, port: self.port.clone() };
|
||||
self.thread_pool.submit(task);
|
||||
let new_state = StreamState::Busy {
|
||||
handler: new_handler,
|
||||
queue: VecDeque::new(),
|
||||
closing: false,
|
||||
};
|
||||
(new_state, Ok(()))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dispatch(&mut self, event: IOEvent<Cmd>) -> Option<H::Product> {
|
||||
let IOEvent { handle, result, stream } = event;
|
||||
let id = handle.id();
|
||||
let state =
|
||||
(self.streams.remove(&id)).expect("Event dispatched on unknown stream");
|
||||
let (handler, mut queue, closing) = match state {
|
||||
StreamState::Busy { handler, queue, closing } =>
|
||||
(handler, queue, closing),
|
||||
_ => panic!("Event dispatched but the source isn't locked"),
|
||||
};
|
||||
if let Some((cmd, handler)) = queue.pop_front() {
|
||||
let port = self.port.clone();
|
||||
self.thread_pool.submit(IOTask { handle, stream, cmd, port });
|
||||
self.streams.insert(id, StreamState::Busy { handler, queue, closing });
|
||||
} else if closing {
|
||||
self.dispose_stream(stream)
|
||||
} else {
|
||||
self.streams.insert(id, StreamState::Free(stream));
|
||||
};
|
||||
Some(handler.handle(result))
|
||||
}
|
||||
}
|
||||
160
src/systems/io/instances.rs
Normal file
160
src/systems/io/instances.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
use std::io::{self, BufRead, BufReader, Read, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::flow::{IOCmd, IOHandler, IOManager, StreamHandle};
|
||||
use crate::foreign::Atomic;
|
||||
use crate::interpreted::ExprInst;
|
||||
use crate::systems::codegen::call;
|
||||
use crate::systems::stl::Binary;
|
||||
use crate::{atomic_inert, Literal};
|
||||
|
||||
pub type Source = BufReader<Box<dyn Read + Send>>;
|
||||
pub type Sink = Box<dyn Write + Send>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct SourceHandle(usize);
|
||||
atomic_inert!(SourceHandle, "an input stream handle");
|
||||
impl StreamHandle for SourceHandle {
|
||||
fn new(id: usize) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
fn id(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct SinkHandle(usize);
|
||||
atomic_inert!(SinkHandle, "an output stream handle");
|
||||
impl StreamHandle for SinkHandle {
|
||||
fn new(id: usize) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
fn id(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// String reading command
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SRead {
|
||||
All,
|
||||
Line,
|
||||
}
|
||||
|
||||
/// Binary reading command
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum BRead {
|
||||
All,
|
||||
N(usize),
|
||||
Until(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ReadCmd {
|
||||
RBytes(BRead),
|
||||
RStr(SRead),
|
||||
}
|
||||
|
||||
impl IOCmd for ReadCmd {
|
||||
type Stream = Source;
|
||||
type Result = ReadResult;
|
||||
type Handle = SourceHandle;
|
||||
|
||||
// This is a buggy rule, check manually
|
||||
#[allow(clippy::read_zero_byte_vec)]
|
||||
fn execute(self, stream: &mut Self::Stream) -> Self::Result {
|
||||
match self {
|
||||
Self::RBytes(bread) => {
|
||||
let mut buf = Vec::new();
|
||||
let result = match &bread {
|
||||
BRead::All => stream.read_to_end(&mut buf).map(|_| ()),
|
||||
BRead::Until(b) => stream.read_until(*b, &mut buf).map(|_| ()),
|
||||
BRead::N(n) => {
|
||||
buf.resize(*n, 0);
|
||||
stream.read_exact(&mut buf)
|
||||
},
|
||||
};
|
||||
ReadResult::RBin(bread, result.map(|_| buf))
|
||||
},
|
||||
Self::RStr(sread) => {
|
||||
let mut buf = String::new();
|
||||
let sresult = match &sread {
|
||||
SRead::All => stream.read_to_string(&mut buf),
|
||||
SRead::Line => stream.read_line(&mut buf),
|
||||
};
|
||||
ReadResult::RStr(sread, sresult.map(|_| buf))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reading command (string or binary)
|
||||
pub enum ReadResult {
|
||||
RStr(SRead, io::Result<String>),
|
||||
RBin(BRead, io::Result<Vec<u8>>),
|
||||
}
|
||||
|
||||
impl IOHandler<ReadCmd> for (ExprInst, ExprInst) {
|
||||
type Product = ExprInst;
|
||||
|
||||
fn handle(self, result: ReadResult) -> Self::Product {
|
||||
let (succ, fail) = self;
|
||||
match result {
|
||||
ReadResult::RBin(_, Err(e)) | ReadResult::RStr(_, Err(e)) =>
|
||||
call(fail, vec![wrap_io_error(e)]).wrap(),
|
||||
ReadResult::RBin(_, Ok(bytes)) =>
|
||||
call(succ, vec![Binary(Arc::new(bytes)).atom_cls().wrap()]).wrap(),
|
||||
ReadResult::RStr(_, Ok(text)) =>
|
||||
call(succ, vec![Literal::Str(text).into()]).wrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Placeholder function for an eventual conversion from [io::Error] to Orchid
|
||||
/// data
|
||||
fn wrap_io_error(_e: io::Error) -> ExprInst {
|
||||
Literal::Uint(0u64).into()
|
||||
}
|
||||
|
||||
pub type ReadManager<P> = IOManager<P, ReadCmd, (ExprInst, ExprInst)>;
|
||||
|
||||
/// Writing command (string or binary)
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum WriteCmd {
|
||||
WBytes(Binary),
|
||||
WStr(String),
|
||||
Flush,
|
||||
}
|
||||
|
||||
impl IOCmd for WriteCmd {
|
||||
type Stream = Sink;
|
||||
type Handle = SinkHandle;
|
||||
type Result = WriteResult;
|
||||
|
||||
fn execute(self, stream: &mut Self::Stream) -> Self::Result {
|
||||
let result = match &self {
|
||||
Self::Flush => stream.flush(),
|
||||
Self::WStr(str) => write!(stream, "{}", str).map(|_| ()),
|
||||
Self::WBytes(bytes) => stream.write_all(bytes.0.as_ref()).map(|_| ()),
|
||||
};
|
||||
WriteResult { result, cmd: self }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WriteResult {
|
||||
pub cmd: WriteCmd,
|
||||
pub result: io::Result<()>,
|
||||
}
|
||||
impl IOHandler<WriteCmd> for (ExprInst, ExprInst) {
|
||||
type Product = ExprInst;
|
||||
|
||||
fn handle(self, result: WriteResult) -> Self::Product {
|
||||
let (succ, fail) = self;
|
||||
match result.result {
|
||||
Ok(_) => succ,
|
||||
Err(e) => call(fail, vec![wrap_io_error(e)]).wrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type WriteManager<P> = IOManager<P, WriteCmd, (ExprInst, ExprInst)>;
|
||||
31
src/systems/io/io.orc
Normal file
31
src/systems/io/io.orc
Normal file
@@ -0,0 +1,31 @@
|
||||
import std::panic
|
||||
import system::io
|
||||
import system::async::yield
|
||||
|
||||
export const print := \text.\ok. (
|
||||
io::write_str io::stdout text
|
||||
(io::flush io::stdout
|
||||
ok
|
||||
(\e. panic "println threw on flush")
|
||||
yield
|
||||
)
|
||||
(\e. panic "print threw on write")
|
||||
yield
|
||||
)
|
||||
|
||||
export const println := \line.\ok. (
|
||||
print (line ++ "\n") ok
|
||||
)
|
||||
|
||||
export const readln := \ok. (
|
||||
io::read_line io::stdin
|
||||
ok
|
||||
(\e. panic "readln threw")
|
||||
yield
|
||||
)
|
||||
|
||||
export module prelude (
|
||||
import super::*
|
||||
|
||||
export ::(print, println, readln)
|
||||
)
|
||||
6
src/systems/io/mod.rs
Normal file
6
src/systems/io/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
mod bindings;
|
||||
mod facade;
|
||||
mod flow;
|
||||
mod instances;
|
||||
|
||||
pub use facade::{io_system, IOStream, IOSystem};
|
||||
13
src/systems/mod.rs
Normal file
13
src/systems/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
//! Constants exposed to usercode by the interpreter
|
||||
mod assertion_error;
|
||||
mod asynch;
|
||||
pub mod cast_exprinst;
|
||||
pub mod codegen;
|
||||
mod io;
|
||||
mod runtime_error;
|
||||
pub mod stl;
|
||||
|
||||
pub use assertion_error::AssertionError;
|
||||
pub use asynch::{AsynchConfig, InfiniteBlock, MessagePort};
|
||||
pub use io::{io_system, IOStream, IOSystem};
|
||||
pub use runtime_error::RuntimeError;
|
||||
35
src/systems/runtime_error.rs
Normal file
35
src/systems/runtime_error.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use std::fmt::Display;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::foreign::ExternError;
|
||||
|
||||
/// Some external event prevented the operation from succeeding
|
||||
#[derive(Clone)]
|
||||
pub struct RuntimeError {
|
||||
message: String,
|
||||
operation: &'static str,
|
||||
}
|
||||
|
||||
impl RuntimeError {
|
||||
/// Construct, upcast and wrap in a Result that never succeeds for easy
|
||||
/// short-circuiting
|
||||
pub fn fail<T>(
|
||||
message: String,
|
||||
operation: &'static str,
|
||||
) -> Result<T, Rc<dyn ExternError>> {
|
||||
return Err(Self { message, operation }.into_extern());
|
||||
}
|
||||
|
||||
/// Construct and upcast to [ExternError]
|
||||
pub fn ext(message: String, operation: &'static str) -> Rc<dyn ExternError> {
|
||||
return Self { message, operation }.into_extern();
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for RuntimeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Error while {}: {}", self.operation, self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternError for RuntimeError {}
|
||||
28
src/systems/stl/arithmetic_error.rs
Normal file
28
src/systems/stl/arithmetic_error.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::foreign::ExternError;
|
||||
|
||||
/// Various errors produced by arithmetic operations
|
||||
pub enum ArithmeticError {
|
||||
/// Integer overflow
|
||||
Overflow,
|
||||
/// Float overflow
|
||||
Infinity,
|
||||
/// Division or modulo by zero
|
||||
DivByZero,
|
||||
/// Other, unexpected operation produced NaN
|
||||
NaN,
|
||||
}
|
||||
|
||||
impl Display for ArithmeticError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::NaN => write!(f, "Operation resulted in NaN"),
|
||||
Self::Overflow => write!(f, "Integer overflow"),
|
||||
Self::Infinity => write!(f, "Operation resulted in Infinity"),
|
||||
Self::DivByZero => write!(f, "A division by zero was attempted"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternError for ArithmeticError {}
|
||||
163
src/systems/stl/bin.rs
Normal file
163
src/systems/stl/bin.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::Boolean;
|
||||
use crate::interpreted::ExprInst;
|
||||
use crate::systems::cast_exprinst::with_uint;
|
||||
use crate::systems::codegen::{orchid_opt, tuple};
|
||||
use crate::systems::RuntimeError;
|
||||
use crate::utils::{iter_find, unwrap_or};
|
||||
use crate::{atomic_inert, define_fn, ConstTree, Interner, Literal};
|
||||
|
||||
/// A block of binary data
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub struct Binary(pub Arc<Vec<u8>>);
|
||||
atomic_inert!(Binary, "a binary blob");
|
||||
|
||||
impl Debug for Binary {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut iter = self.0.iter().copied();
|
||||
f.write_str("Binary")?;
|
||||
for mut chunk in iter.by_ref().take(32).chunks(4).into_iter() {
|
||||
let a = chunk.next().expect("Chunks cannot be empty");
|
||||
let b = unwrap_or!(chunk.next(); return write!(f, "{a:02x}"));
|
||||
let c = unwrap_or!(chunk.next(); return write!(f, "{a:02x}{b:02x}"));
|
||||
let d =
|
||||
unwrap_or!(chunk.next(); return write!(f, "{a:02x}{b:02x}{c:02x}"));
|
||||
write!(f, "{a:02x}{b:02x}{c:02x}{d:02x}")?
|
||||
}
|
||||
if iter.next().is_some() { write!(f, "...") } else { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Convert a number into a binary blob
|
||||
pub FromNum {
|
||||
size: u64,
|
||||
is_little_endian: Boolean,
|
||||
data: u64
|
||||
} => {
|
||||
if size > &8 {
|
||||
RuntimeError::fail(
|
||||
"more than 8 bytes requested".to_string(),
|
||||
"converting number to binary"
|
||||
)?
|
||||
}
|
||||
let bytes = if is_little_endian.0 {
|
||||
data.to_le_bytes()[0..*size as usize].to_vec()
|
||||
} else {
|
||||
data.to_be_bytes()[8 - *size as usize..].to_vec()
|
||||
};
|
||||
Ok(Binary(Arc::new(bytes)).atom_cls())
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Read a number from a binary blob
|
||||
pub GetNum {
|
||||
buf: Binary,
|
||||
loc: u64,
|
||||
size: u64,
|
||||
is_little_endian: Boolean
|
||||
} => {
|
||||
if buf.0.len() < (loc + size) as usize {
|
||||
RuntimeError::fail(
|
||||
"section out of range".to_string(),
|
||||
"reading number from binary data"
|
||||
)?
|
||||
}
|
||||
if 8 < *size {
|
||||
RuntimeError::fail(
|
||||
"more than 8 bytes provided".to_string(),
|
||||
"reading number from binary data"
|
||||
)?
|
||||
}
|
||||
let mut data = [0u8; 8];
|
||||
let section = &buf.0[*loc as usize..(loc + size) as usize];
|
||||
let num = if is_little_endian.0 {
|
||||
data[0..*size as usize].copy_from_slice(section);
|
||||
u64::from_le_bytes(data)
|
||||
} else {
|
||||
data[8 - *size as usize..].copy_from_slice(section);
|
||||
u64::from_be_bytes(data)
|
||||
};
|
||||
Ok(Literal::Uint(num).into())
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Append two binary data blocks
|
||||
pub Concatenate { a: Binary, b: Binary } => {
|
||||
let data = a.0.iter().chain(b.0.iter()).copied().collect();
|
||||
Ok(Binary(Arc::new(data)).atom_cls())
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Extract a subsection of the binary data
|
||||
pub Slice {
|
||||
s: Binary,
|
||||
i: u64 as with_uint(x, Ok),
|
||||
len: u64 as with_uint(x, Ok)
|
||||
} => {
|
||||
if i + len < s.0.len() as u64 {
|
||||
RuntimeError::fail(
|
||||
"Byte index out of bounds".to_string(),
|
||||
"indexing binary"
|
||||
)?
|
||||
}
|
||||
let data = s.0[*i as usize..*i as usize + *len as usize].to_vec();
|
||||
Ok(Binary(Arc::new(data)).atom_cls())
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Return the index where the first argument first contains the second,
|
||||
/// if any
|
||||
pub Find { haystack: Binary, needle: Binary } => {
|
||||
let found = iter_find(haystack.0.iter(), needle.0.iter());
|
||||
Ok(orchid_opt(found.map(|x| Literal::Uint(x as u64).into())))
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Split binary data block into two smaller blocks
|
||||
pub Split {
|
||||
bin: Binary,
|
||||
i: u64 as with_uint(x, Ok)
|
||||
} => {
|
||||
if bin.0.len() < *i as usize {
|
||||
RuntimeError::fail(
|
||||
"Byte index out of bounds".to_string(),
|
||||
"splitting binary"
|
||||
)?
|
||||
}
|
||||
let (asl, bsl) = bin.0.split_at(*i as usize);
|
||||
Ok(tuple(vec![
|
||||
Binary(Arc::new(asl.to_vec())).atom_cls().into(),
|
||||
Binary(Arc::new(bsl.to_vec())).atom_cls().into(),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Detect the number of bytes in the binary data block
|
||||
pub Size = |x| {
|
||||
Ok(Literal::Uint(Binary::try_from(x)?.0.len() as u64).into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bin(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(
|
||||
i.i("bin"),
|
||||
ConstTree::tree([
|
||||
(i.i("concat"), ConstTree::xfn(Concatenate)),
|
||||
(i.i("slice"), ConstTree::xfn(Slice)),
|
||||
(i.i("find"), ConstTree::xfn(Find)),
|
||||
(i.i("split"), ConstTree::xfn(Split)),
|
||||
(i.i("size"), ConstTree::xfn(Size)),
|
||||
]),
|
||||
)])
|
||||
}
|
||||
6
src/systems/stl/bool.orc
Normal file
6
src/systems/stl/bool.orc
Normal file
@@ -0,0 +1,6 @@
|
||||
export const not := \bool. if bool then false else true
|
||||
export macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b))
|
||||
export macro ...$a == ...$b =0x3p36=> (equals (...$a) (...$b))
|
||||
export macro if ...$cond then ...$true else ...$false:1 =0x1p84=> (
|
||||
ifthenelse (...$cond) (...$true) (...$false)
|
||||
)
|
||||
66
src/systems/stl/bool.rs
Normal file
66
src/systems/stl/bool.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::interner::Interner;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::systems::AssertionError;
|
||||
use crate::{atomic_inert, define_fn, ConstTree, Literal, PathSet};
|
||||
|
||||
/// Booleans exposed to Orchid
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Boolean(pub bool);
|
||||
atomic_inert!(Boolean, "a boolean");
|
||||
|
||||
impl From<bool> for Boolean {
|
||||
fn from(value: bool) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Compares the inner values if
|
||||
///
|
||||
/// - both are string,
|
||||
/// - both are either uint or num
|
||||
Equals { a: Literal, b: Literal } => Ok(Boolean::from(match (a, b) {
|
||||
(Literal::Str(s1), Literal::Str(s2)) => s1 == s2,
|
||||
(Literal::Num(n1), Literal::Num(n2)) => n1 == n2,
|
||||
(Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2,
|
||||
(Literal::Num(n1), Literal::Uint(u1)) => *n1 == (*u1 as f64),
|
||||
(Literal::Uint(u1), Literal::Num(n1)) => *n1 == (*u1 as f64),
|
||||
(..) => AssertionError::fail(b.clone().into(), "the expected type")?,
|
||||
}).atom_cls())
|
||||
}
|
||||
|
||||
// Even though it's a ternary function, IfThenElse is implemented as an unary
|
||||
// foreign function, as the rest of the logic can be defined in Orchid.
|
||||
define_fn! {
|
||||
/// Takes a boolean and two branches, runs the first if the bool is true, the
|
||||
/// second if it's false.
|
||||
IfThenElse = |x| x.try_into()
|
||||
.map_err(|_| AssertionError::ext(x.clone(), "a boolean"))
|
||||
.map(|b: Boolean| if b.0 {Clause::Lambda {
|
||||
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
|
||||
body: Clause::Lambda {
|
||||
args: None,
|
||||
body: Clause::LambdaArg.wrap()
|
||||
}.wrap(),
|
||||
}} else {Clause::Lambda {
|
||||
args: None,
|
||||
body: Clause::Lambda {
|
||||
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
|
||||
body: Clause::LambdaArg.wrap(),
|
||||
}.wrap(),
|
||||
}})
|
||||
}
|
||||
|
||||
pub fn bool(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(
|
||||
i.i("bool"),
|
||||
ConstTree::tree([
|
||||
(i.i("ifthenelse"), ConstTree::xfn(IfThenElse)),
|
||||
(i.i("equals"), ConstTree::xfn(Equals)),
|
||||
(i.i("true"), ConstTree::atom(Boolean(true))),
|
||||
(i.i("false"), ConstTree::atom(Boolean(false))),
|
||||
]),
|
||||
)])
|
||||
}
|
||||
58
src/systems/stl/conv.rs
Normal file
58
src/systems/stl/conv.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use chumsky::Parser;
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::ArithmeticError;
|
||||
use crate::foreign::ExternError;
|
||||
use crate::interner::Interner;
|
||||
use crate::parse::{float_parser, int_parser};
|
||||
use crate::systems::cast_exprinst::with_lit;
|
||||
use crate::systems::AssertionError;
|
||||
use crate::{define_fn, ConstTree, Literal};
|
||||
|
||||
define_fn! {
|
||||
/// parse a number. Accepts the same syntax Orchid does.
|
||||
ToFloat = |x| with_lit(x, |l| match l {
|
||||
Literal::Str(s) => float_parser()
|
||||
.parse(s.as_str())
|
||||
.map_err(|_| AssertionError::ext(
|
||||
x.clone(),
|
||||
"cannot be parsed into a float"
|
||||
)),
|
||||
Literal::Num(n) => Ok(*n),
|
||||
Literal::Uint(i) => NotNan::new(*i as f64)
|
||||
.map_err(|_| ArithmeticError::NaN.into_extern()),
|
||||
}).map(|nn| Literal::Num(nn).into())
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Parse an unsigned integer. Accepts the same formats Orchid does. If the
|
||||
/// input is a number, floors it.
|
||||
ToUint = |x| with_lit(x, |l| match l {
|
||||
Literal::Str(s) => int_parser()
|
||||
.parse(s.as_str())
|
||||
.map_err(|_| AssertionError::ext(
|
||||
x.clone(),
|
||||
"cannot be parsed into an unsigned int",
|
||||
)),
|
||||
Literal::Num(n) => Ok(n.floor() as u64),
|
||||
Literal::Uint(i) => Ok(*i),
|
||||
}).map(|u| Literal::Uint(u).into())
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Convert a literal to a string using Rust's conversions for floats, chars and
|
||||
/// uints respectively
|
||||
ToString = |x| with_lit(x, |l| Ok(match l {
|
||||
Literal::Uint(i) => i.to_string(),
|
||||
Literal::Num(n) => n.to_string(),
|
||||
Literal::Str(s) => s.clone(),
|
||||
})).map(|s| Literal::Str(s).into())
|
||||
}
|
||||
|
||||
pub fn conv(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("to_float"), ConstTree::xfn(ToFloat)),
|
||||
(i.i("to_uint"), ConstTree::xfn(ToUint)),
|
||||
(i.i("to_string"), ConstTree::xfn(ToString)),
|
||||
])
|
||||
}
|
||||
26
src/systems/stl/fn.orc
Normal file
26
src/systems/stl/fn.orc
Normal file
@@ -0,0 +1,26 @@
|
||||
import super::known::*
|
||||
|
||||
--[ Do nothing. Especially useful as a passive cps operation ]--
|
||||
export const identity := \x.x
|
||||
--[
|
||||
Apply the function to the given value. Can be used to assign a
|
||||
concrete value in a cps assignment statement.
|
||||
]--
|
||||
export const pass := \val.\cont. cont val
|
||||
--[
|
||||
Apply the function to the given pair of values. Mainly useful to assign
|
||||
a concrete pair of values in a cps multi-assignment statement
|
||||
]--
|
||||
export const pass2 := \a.\b.\cont. cont a b
|
||||
--[
|
||||
A function that returns the given value for any input. Also useful as a
|
||||
"break" statement in a "do" block.
|
||||
]--
|
||||
export const return := \a. \b.a
|
||||
|
||||
export macro ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix)
|
||||
export macro ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix
|
||||
|
||||
export macro ($name) => ...$body =0x2p129=> (\$name. ...$body)
|
||||
export macro ($name, ...$argv) => ...$body =0x2p129=> (\$name. (...$argv) => ...$body)
|
||||
macro $name => ...$body =0x1p129=> (\$name. ...$body)
|
||||
33
src/systems/stl/inspect.rs
Normal file
33
src/systems/stl/inspect.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::foreign::{Atomic, AtomicReturn};
|
||||
use crate::interner::InternedDisplay;
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_defaults, write_fn_step, ConstTree, Interner};
|
||||
|
||||
write_fn_step! {
|
||||
/// Print and return whatever expression is in the argument without
|
||||
/// normalizing it.
|
||||
Inspect > Inspect1
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Inspect1 {
|
||||
expr_inst: ExprInst,
|
||||
}
|
||||
impl Atomic for Inspect1 {
|
||||
atomic_defaults!();
|
||||
fn run(&self, ctx: Context) -> crate::foreign::AtomicResult {
|
||||
println!("{}", self.expr_inst.bundle(ctx.interner));
|
||||
Ok(AtomicReturn {
|
||||
clause: self.expr_inst.expr().clause.clone(),
|
||||
gas: ctx.gas.map(|g| g - 1),
|
||||
inert: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inspect(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(i.i("inspect"), ConstTree::xfn(Inspect))])
|
||||
}
|
||||
1
src/systems/stl/known.orc
Normal file
1
src/systems/stl/known.orc
Normal file
@@ -0,0 +1 @@
|
||||
export ::[,]
|
||||
107
src/systems/stl/list.orc
Normal file
107
src/systems/stl/list.orc
Normal file
@@ -0,0 +1,107 @@
|
||||
import super::(option, fn::*, proc::*, loop::*, bool::*, known::*, num::*)
|
||||
|
||||
const pair := \a.\b. \f. f a b
|
||||
|
||||
-- Constructors
|
||||
|
||||
export const cons := \hd.\tl. option::some (pair hd tl)
|
||||
export const end := option::none
|
||||
|
||||
export const pop := \list.\default.\f.list default \cons.cons f
|
||||
|
||||
-- Operators
|
||||
|
||||
--[
|
||||
Fold each element into an accumulator using an `acc -> el -> acc`.
|
||||
This evaluates the entire list, and is always tail recursive.
|
||||
]--
|
||||
export const fold := \list.\acc.\f. (
|
||||
loop_over (list, acc) {
|
||||
cps head, list = pop list acc;
|
||||
let acc = f acc head;
|
||||
}
|
||||
)
|
||||
|
||||
--[
|
||||
Fold each element into an accumulator in reverse order.
|
||||
This evaulates the entire list, and is never tail recursive.
|
||||
]--
|
||||
export const rfold := \list.\acc.\f. (
|
||||
recursive r (list)
|
||||
pop list acc \head.\tail.
|
||||
f (r tail) head
|
||||
)
|
||||
|
||||
--[
|
||||
Fold each element into a shared element with an `el -> el -> el`.
|
||||
This evaluates the entire list, and is never tail recursive.
|
||||
]--
|
||||
export const reduce := \list.\f. do{
|
||||
cps head, list = pop list option::none;
|
||||
option::some $ fold list head f
|
||||
}
|
||||
|
||||
--[
|
||||
Return a new list that contains only the elements from the input list
|
||||
for which the function returns true. This operation is lazy.
|
||||
]--
|
||||
export const filter := \list.\f. (
|
||||
pop list end \head.\tail.
|
||||
if (f el)
|
||||
then cons el (filter tail f)
|
||||
else filter tail f
|
||||
)
|
||||
|
||||
--[
|
||||
Transform each element of the list with an `el -> any`.
|
||||
]--
|
||||
export const map := \list.\f. (
|
||||
recursive r (list)
|
||||
pop list end \head.\tail.
|
||||
cons (f head) (r tail)
|
||||
)
|
||||
|
||||
--[
|
||||
Skip `n` elements from the list and return the tail
|
||||
If `n` is not an integer, this returns `end`.
|
||||
]--
|
||||
export const skip := \foo.\n. (
|
||||
loop_over (foo, n) {
|
||||
cps _head, foo = if n == 0
|
||||
then return foo
|
||||
else pop foo end;
|
||||
let n = n - 1;
|
||||
}
|
||||
)
|
||||
|
||||
--[
|
||||
Return `n` elements from the list and discard the rest.
|
||||
This operation is lazy.
|
||||
]--
|
||||
export const take := \list.\n. (
|
||||
recursive r (list, n)
|
||||
if n == 0
|
||||
then end
|
||||
else pop list end \head.\tail.
|
||||
cons head $ r tail $ n - 1
|
||||
)
|
||||
|
||||
--[
|
||||
Return the `n`th element from the list.
|
||||
This operation is tail recursive.
|
||||
]--
|
||||
export const get := \list.\n. (
|
||||
loop_over (list, n) {
|
||||
cps head, list = pop list option::none;
|
||||
cps if n == 0
|
||||
then return (option::some head)
|
||||
else identity;
|
||||
let n = n - 1;
|
||||
}
|
||||
)
|
||||
|
||||
macro new[...$item, ...$rest:1] =0x2p84=> (cons (...$item) new[...$rest])
|
||||
macro new[...$end] =0x1p84=> (cons (...$end) end)
|
||||
macro new[] =0x1p84=> end
|
||||
|
||||
export ::(new)
|
||||
63
src/systems/stl/loop.orc
Normal file
63
src/systems/stl/loop.orc
Normal file
@@ -0,0 +1,63 @@
|
||||
import super::proc::(;, do, =)
|
||||
import super::known::*
|
||||
|
||||
--[
|
||||
Bare fixpoint combinator. Due to its many pitfalls, usercode is
|
||||
recommended to use one of the wrappers such as [recursive] or
|
||||
[loop_over] instead.
|
||||
]--
|
||||
export const Y := \f.(\x.f (x x))(\x.f (x x))
|
||||
|
||||
--[
|
||||
A syntax construct that encapsulates the Y combinator and encourages
|
||||
single tail recursion. It's possible to use this for multiple or
|
||||
non-tail recursion by using cps statements, but it's more ergonomic
|
||||
than [Y] and more flexible than [std::list::fold].
|
||||
|
||||
To break out of the loop, use [std::fn::const] in a cps statement
|
||||
]--
|
||||
export macro loop_over (..$binds) {
|
||||
...$body
|
||||
} =0x5p129=> Y (\r.
|
||||
def_binds parse_binds (..$binds) do{
|
||||
...$body;
|
||||
r apply_binds parse_binds (..$binds)
|
||||
}
|
||||
) init_binds parse_binds (..$binds)
|
||||
|
||||
-- parse_binds builds a conslist
|
||||
macro parse_binds (...$item, ...$tail:1) =0x2p250=> (
|
||||
parse_bind (...$item)
|
||||
parse_binds (...$tail)
|
||||
)
|
||||
macro parse_binds (...$item) =0x1p250=> (
|
||||
parse_bind (...$item)
|
||||
()
|
||||
)
|
||||
|
||||
-- parse_bind converts items to pairs
|
||||
macro parse_bind ($name) =0x1p250=> ($name bind_no_value)
|
||||
macro parse_bind ($name = ...$value) =0x1p250=> ($name (...$value))
|
||||
|
||||
-- def_binds creates name bindings for everything
|
||||
macro def_binds ( ($name $value) $tail ) ...$body =0x1p250=> (
|
||||
\$name. def_binds $tail ...$body
|
||||
)
|
||||
macro def_binds () ...$body =0x1p250=> ...$body
|
||||
|
||||
-- init_binds passes the value for initializers
|
||||
macro init_binds ( ($name bind_no_value) $tail ) =0x2p250=> $name init_binds $tail
|
||||
macro init_binds ( ($name $value) $tail ) =0x1p250=> $value init_binds $tail
|
||||
-- avoid empty templates by assuming that there is a previous token
|
||||
macro $fn init_binds () =0x1p250=> $fn
|
||||
|
||||
-- apply_binds passes the name for initializers
|
||||
macro apply_binds ( ($name $_value) $tail ) =0x1p250=> $name apply_binds $tail
|
||||
macro $fn apply_binds () =0x1p250=> $fn
|
||||
|
||||
--[
|
||||
Alias for the Y-combinator to avoid some universal pitfalls
|
||||
]--
|
||||
export macro recursive $name (..$binds) ...$body =0x5p129=> Y (\$name.
|
||||
def_binds parse_binds (..$binds) ...$body
|
||||
) init_binds parse_binds (..$binds)
|
||||
73
src/systems/stl/map.orc
Normal file
73
src/systems/stl/map.orc
Normal file
@@ -0,0 +1,73 @@
|
||||
import super::(bool::*, fn::*, known::*, list, option, loop::*, proc::*)
|
||||
import std::panic
|
||||
|
||||
-- utilities for using lists as pairs
|
||||
|
||||
export const fst := \l. (
|
||||
list::get l 0
|
||||
(panic "nonempty expected")
|
||||
\x.x
|
||||
)
|
||||
export const snd := \l. (
|
||||
list::get l 1
|
||||
(panic "2 elements expected")
|
||||
\x.x
|
||||
)
|
||||
|
||||
-- constructors
|
||||
|
||||
export const empty := list::end
|
||||
export const add := \m.\k.\v. (
|
||||
list::cons
|
||||
list::new[k, v]
|
||||
m
|
||||
)
|
||||
|
||||
-- queries
|
||||
|
||||
-- return the last occurrence of a key if exists
|
||||
export const get := \m.\key. (
|
||||
loop_over (m) {
|
||||
cps record, m = list::pop m option::none;
|
||||
cps if fst record == key
|
||||
then return $ option::some $ snd record
|
||||
else identity;
|
||||
}
|
||||
)
|
||||
|
||||
-- commands
|
||||
|
||||
-- remove one occurrence of a key
|
||||
export const del := \m.\k. (
|
||||
recursive r (m)
|
||||
list::pop m list::end \head.\tail.
|
||||
if fst head == k then tail
|
||||
else list::cons head $ r tail
|
||||
)
|
||||
|
||||
-- remove all occurrences of a key
|
||||
export const delall := \m.\k. (
|
||||
list::filter m \record. fst record != k
|
||||
)
|
||||
|
||||
-- replace at most one occurrence of a key
|
||||
export const set := \m.\k.\v. (
|
||||
m
|
||||
|> del k
|
||||
|> add k v
|
||||
)
|
||||
|
||||
-- ensure that there's only one instance of each key in the map
|
||||
export const normalize := \m. (
|
||||
recursive r (m, normal=empty) with
|
||||
list::pop m normal \head.\tail.
|
||||
r tail $ set normal (fst head) (snd head)
|
||||
)
|
||||
|
||||
macro new[...$tail:2, ...$key = ...$value:1] =0x2p84=> (
|
||||
set new[...$tail] (...$key) (...$value)
|
||||
)
|
||||
macro new[...$key = ...$value:1] =0x1p84=> (add empty (...$key) (...$value))
|
||||
macro new[] =0x1p84=> empty
|
||||
|
||||
export ::(new)
|
||||
17
src/systems/stl/mod.rs
Normal file
17
src/systems/stl/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
//! Basic types and their functions, frequently used tools with no environmental
|
||||
//! dependencies.
|
||||
mod arithmetic_error;
|
||||
mod bin;
|
||||
mod bool;
|
||||
mod conv;
|
||||
mod inspect;
|
||||
mod num;
|
||||
mod panic;
|
||||
mod stl_system;
|
||||
mod str;
|
||||
pub use arithmetic_error::ArithmeticError;
|
||||
pub use bin::Binary;
|
||||
pub use num::Numeric;
|
||||
pub use stl_system::StlConfig;
|
||||
|
||||
pub use self::bool::Boolean;
|
||||
5
src/systems/stl/num.orc
Normal file
5
src/systems/stl/num.orc
Normal file
@@ -0,0 +1,5 @@
|
||||
export macro ...$a + ...$b =0x2p36=> (add (...$a) (...$b))
|
||||
export macro ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b))
|
||||
export macro ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b))
|
||||
export macro ...$a % ...$b:1 =0x1p36=> (remainder (...$a) (...$b))
|
||||
export macro ...$a / ...$b:1 =0x1p36=> (divide (...$a) (...$b))
|
||||
148
src/systems/stl/num.rs
Normal file
148
src/systems/stl/num.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::ArithmeticError;
|
||||
use crate::foreign::ExternError;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::{Literal, Primitive};
|
||||
use crate::systems::cast_exprinst::with_lit;
|
||||
use crate::systems::AssertionError;
|
||||
use crate::{define_fn, ConstTree, Interner};
|
||||
|
||||
// region: Numeric, type to handle floats and uints together
|
||||
|
||||
/// A number, either floating point or unsigned int, visible to Orchid.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Numeric {
|
||||
/// A nonnegative integer such as a size, index or count
|
||||
Uint(u64),
|
||||
/// A float other than NaN. Orchid has no silent errors
|
||||
Num(NotNan<f64>),
|
||||
}
|
||||
|
||||
impl Numeric {
|
||||
fn as_f64(&self) -> f64 {
|
||||
match self {
|
||||
Numeric::Num(n) => **n,
|
||||
Numeric::Uint(i) => *i as f64,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a f64 in a Numeric
|
||||
fn num(value: f64) -> Result<Self, Rc<dyn ExternError>> {
|
||||
if value.is_finite() {
|
||||
NotNan::new(value)
|
||||
.map(Self::Num)
|
||||
.map_err(|_| ArithmeticError::NaN.into_extern())
|
||||
} else {
|
||||
Err(ArithmeticError::Infinity.into_extern())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&ExprInst> for Numeric {
|
||||
type Error = Rc<dyn ExternError>;
|
||||
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||
with_lit(value, |l| match l {
|
||||
Literal::Uint(i) => Ok(Numeric::Uint(*i)),
|
||||
Literal::Num(n) => Ok(Numeric::Num(*n)),
|
||||
_ => AssertionError::fail(value.clone(), "an integer or number")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Numeric> for Clause {
|
||||
fn from(value: Numeric) -> Self {
|
||||
Clause::P(Primitive::Literal(match value {
|
||||
Numeric::Uint(i) => Literal::Uint(i),
|
||||
Numeric::Num(n) => Literal::Num(n),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: operations
|
||||
|
||||
define_fn! {
|
||||
/// Add two numbers. If they're both uint, the output is uint. If either is
|
||||
/// number, the output is number.
|
||||
Add { a: Numeric, b: Numeric } => match (a, b) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => {
|
||||
a.checked_add(*b)
|
||||
.map(Numeric::Uint)
|
||||
.ok_or_else(|| ArithmeticError::Overflow.into_extern())
|
||||
}
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a + b)),
|
||||
(Numeric::Num(a), Numeric::Uint(b)) | (Numeric::Uint(b), Numeric::Num(a))
|
||||
=> Numeric::num(a.into_inner() + *b as f64),
|
||||
}.map(Numeric::into)
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Subtract a number from another. Always returns Number.
|
||||
Subtract { a: Numeric, b: Numeric } => match (a, b) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::num(*a as f64 - *b as f64),
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a - b)),
|
||||
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(**a - *b as f64),
|
||||
(Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(*a as f64 - **b),
|
||||
}.map(Numeric::into)
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Multiply two numbers. If they're both uint, the output is uint. If either
|
||||
/// is number, the output is number.
|
||||
Multiply { a: Numeric, b: Numeric } => match (a, b) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => {
|
||||
a.checked_mul(*b)
|
||||
.map(Numeric::Uint)
|
||||
.ok_or_else(|| ArithmeticError::Overflow.into_extern())
|
||||
}
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a * b)),
|
||||
(Numeric::Uint(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Uint(a))
|
||||
=> Numeric::num(*a as f64 * **b),
|
||||
}.map(Numeric::into)
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Divide a number by another. Always returns Number.
|
||||
Divide { a: Numeric, b: Numeric } => {
|
||||
let a: f64 = a.as_f64();
|
||||
let b: f64 = b.as_f64();
|
||||
if b == 0.0 {
|
||||
return Err(ArithmeticError::DivByZero.into_extern())
|
||||
}
|
||||
Numeric::num(a / b).map(Numeric::into)
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
/// Take the remainder of two numbers. If they're both uint, the output is
|
||||
/// uint. If either is number, the output is number.
|
||||
Remainder { a: Numeric, b: Numeric } => match (a, b) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => {
|
||||
a.checked_rem(*b)
|
||||
.map(Numeric::Uint)
|
||||
.ok_or_else(|| ArithmeticError::DivByZero.into_extern())
|
||||
}
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a % b)),
|
||||
(Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(*a as f64 % **b),
|
||||
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(**a % *b as f64),
|
||||
}.map(Numeric::into)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
pub fn num(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(
|
||||
i.i("num"),
|
||||
ConstTree::tree([
|
||||
(i.i("add"), ConstTree::xfn(Add)),
|
||||
(i.i("subtract"), ConstTree::xfn(Subtract)),
|
||||
(i.i("multiply"), ConstTree::xfn(Multiply)),
|
||||
(i.i("divide"), ConstTree::xfn(Divide)),
|
||||
(i.i("remainder"), ConstTree::xfn(Remainder)),
|
||||
]),
|
||||
)])
|
||||
}
|
||||
9
src/systems/stl/option.orc
Normal file
9
src/systems/stl/option.orc
Normal file
@@ -0,0 +1,9 @@
|
||||
import std::panic
|
||||
|
||||
export const some := \v. \d.\f. f v
|
||||
export const none := \d.\f. d
|
||||
|
||||
export const map := \option.\f. option none f
|
||||
export const flatten := \option. option none \opt. opt
|
||||
export const flatmap := \option.\f. option none \opt. map opt f
|
||||
export const unwrap := \option. option (panic "value expected") \x.x
|
||||
26
src/systems/stl/panic.rs
Normal file
26
src/systems/stl/panic.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::foreign::ExternError;
|
||||
use crate::systems::cast_exprinst::with_str;
|
||||
use crate::{define_fn, ConstTree, Interner};
|
||||
|
||||
/// An unrecoverable error in Orchid land. Because Orchid is lazy, this only
|
||||
/// invalidates expressions that reference the one that generated it.
|
||||
pub struct OrchidPanic(String);
|
||||
|
||||
impl Display for OrchidPanic {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Orchid code panicked: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternError for OrchidPanic {}
|
||||
|
||||
define_fn! {
|
||||
/// Takes a message, returns an [ExternError] unconditionally.
|
||||
Panic = |x| with_str(x, |s| Err(OrchidPanic(s.clone()).into_extern()))
|
||||
}
|
||||
|
||||
pub fn panic(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(i.i("panic"), ConstTree::xfn(Panic))])
|
||||
}
|
||||
17
src/systems/stl/prelude.orc
Normal file
17
src/systems/stl/prelude.orc
Normal file
@@ -0,0 +1,17 @@
|
||||
import std::num::*
|
||||
export ::(+, -, *, /, %)
|
||||
import std::str::*
|
||||
export ::[++]
|
||||
import std::bool::*
|
||||
export ::(==, if, then, else, true, false)
|
||||
import std::fn::*
|
||||
export ::($, |>, =>, identity, pass, pass2, return)
|
||||
import std::list
|
||||
import std::map
|
||||
import std::option
|
||||
export ::(list, map, option)
|
||||
import std::loop::*
|
||||
export ::(loop_over, recursive)
|
||||
|
||||
import std::known::*
|
||||
export ::[,]
|
||||
22
src/systems/stl/proc.orc
Normal file
22
src/systems/stl/proc.orc
Normal file
@@ -0,0 +1,22 @@
|
||||
import super::fn::=>
|
||||
|
||||
-- remove duplicate ;-s
|
||||
export macro do {
|
||||
...$statement ; ; ...$rest:1
|
||||
} =0x3p130=> do {
|
||||
...$statement ; ...$rest
|
||||
}
|
||||
export macro do {
|
||||
...$statement ; ...$rest:1
|
||||
} =0x2p130=> statement (...$statement) do { ...$rest }
|
||||
export macro do { ...$return } =0x1p130=> ...$return
|
||||
|
||||
export macro statement (let $name = ...$value) ...$next =0x1p230=> (
|
||||
( \$name. ...$next) (...$value)
|
||||
)
|
||||
export macro statement (cps ...$names = ...$operation:1) ...$next =0x2p230=> (
|
||||
(...$operation) ( (...$names) => ...$next )
|
||||
)
|
||||
export macro statement (cps ...$operation) ...$next =0x1p230=> (
|
||||
(...$operation) (...$next)
|
||||
)
|
||||
50
src/systems/stl/stl_system.rs
Normal file
50
src/systems/stl/stl_system.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
use hashbrown::HashMap;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
use super::bin::bin;
|
||||
use super::bool::bool;
|
||||
use super::conv::conv;
|
||||
use super::inspect::inspect;
|
||||
use super::num::num;
|
||||
use super::panic::panic;
|
||||
use super::str::str;
|
||||
use crate::facade::{IntoSystem, System};
|
||||
use crate::interner::Interner;
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::pipeline::file_loader::embed_to_map;
|
||||
use crate::sourcefile::{FileEntry, Import};
|
||||
|
||||
/// Feature flags for the STL.
|
||||
#[derive(Default)]
|
||||
pub struct StlConfig {
|
||||
/// Whether impure functions (such as io::debug) are allowed. An embedder
|
||||
/// would typically disable this flag
|
||||
pub impure: bool,
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/systems/stl"]
|
||||
#[prefix = "std/"]
|
||||
#[include = "*.orc"]
|
||||
struct StlEmbed;
|
||||
|
||||
// TODO: fix all orc modules to not rely on prelude
|
||||
|
||||
impl IntoSystem<'static> for StlConfig {
|
||||
fn into_system(self, i: &Interner) -> System<'static> {
|
||||
let pure_fns = conv(i) + bool(i) + str(i) + num(i) + bin(i) + panic(i);
|
||||
let mk_impure_fns = || inspect(i);
|
||||
let fns = if self.impure { pure_fns + mk_impure_fns() } else { pure_fns };
|
||||
System {
|
||||
name: vec!["std".to_string()],
|
||||
constants: HashMap::from([(i.i("std"), fns)]),
|
||||
code: embed_to_map::<StlEmbed>(".orc", i),
|
||||
prelude: vec![FileEntry::Import(vec![Import {
|
||||
path: i.i(&[i.i("std"), i.i("prelude")][..]),
|
||||
name: None,
|
||||
}])],
|
||||
handlers: HandlerTable::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/systems/stl/str.orc
Normal file
10
src/systems/stl/str.orc
Normal file
@@ -0,0 +1,10 @@
|
||||
import super::(proc::*, bool::*, panic)
|
||||
|
||||
export macro ...$a ++ ...$b =0x4p36=> (concat (...$a) (...$b))
|
||||
|
||||
export const char_at := \s.\i. do{
|
||||
let slc = slice s i 1;
|
||||
if len slc == 1
|
||||
then slc
|
||||
else panic "Character index out of bounds"
|
||||
}
|
||||
87
src/systems/stl/str.rs
Normal file
87
src/systems/stl/str.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::interner::Interner;
|
||||
use crate::systems::cast_exprinst::with_str;
|
||||
use crate::systems::codegen::{orchid_opt, tuple};
|
||||
use crate::systems::RuntimeError;
|
||||
use crate::utils::iter_find;
|
||||
use crate::{define_fn, ConstTree, Literal};
|
||||
|
||||
define_fn! {expr=x in
|
||||
/// Append a string to another
|
||||
pub Concatenate { a: String, b: String }
|
||||
=> Ok(Literal::Str(a.to_owned() + b).into())
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
pub Slice { s: String, i: u64, len: u64 } => {
|
||||
let graphs = s.graphemes(true);
|
||||
if *i == 0 {
|
||||
Ok(Literal::Str(graphs.take(*len as usize).collect()).into())
|
||||
} else {
|
||||
let mut prefix = graphs.skip(*i as usize - 1);
|
||||
if prefix.next().is_none() {
|
||||
RuntimeError::fail(
|
||||
"Character index out of bounds".to_string(),
|
||||
"indexing string",
|
||||
)
|
||||
} else {
|
||||
let mut count = 0;
|
||||
let ret = prefix
|
||||
.take(*len as usize)
|
||||
.map(|x| { count+=1; x })
|
||||
.collect();
|
||||
if count == *len {
|
||||
Ok(Literal::Str(ret).into())
|
||||
} else {
|
||||
RuntimeError::fail(
|
||||
"Character index out of bounds".to_string(),
|
||||
"indexing string"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
pub Find { haystack: String, needle: String } => {
|
||||
let found = iter_find(haystack.graphemes(true), needle.graphemes(true));
|
||||
Ok(orchid_opt(found.map(|x| Literal::Uint(x as u64).into())))
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {expr=x in
|
||||
pub Split { s: String, i: u64 } => {
|
||||
let mut graphs = s.graphemes(true);
|
||||
let a = graphs.by_ref().take(*i as usize).collect::<String>();
|
||||
let b = graphs.collect::<String>();
|
||||
Ok(tuple(vec![a.into(), b.into()]))
|
||||
}
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
pub Len = |x| with_str(x, |s| {
|
||||
Ok(Literal::Uint(s.graphemes(true).count() as u64).into())
|
||||
})
|
||||
}
|
||||
|
||||
define_fn! {
|
||||
pub Size = |x| with_str(x, |s| {
|
||||
Ok(Literal::Uint(s.as_bytes().len() as u64).into())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn str(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(
|
||||
i.i("str"),
|
||||
ConstTree::tree([
|
||||
(i.i("concat"), ConstTree::xfn(Concatenate)),
|
||||
(i.i("slice"), ConstTree::xfn(Slice)),
|
||||
(i.i("find"), ConstTree::xfn(Find)),
|
||||
(i.i("split"), ConstTree::xfn(Split)),
|
||||
(i.i("len"), ConstTree::xfn(Len)),
|
||||
(i.i("size"), ConstTree::xfn(Size)),
|
||||
]),
|
||||
)])
|
||||
}
|
||||
Reference in New Issue
Block a user