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:
2023-08-17 20:47:08 +01:00
parent 751a02a1ec
commit 3fdabc29da
139 changed files with 4269 additions and 1783 deletions

View 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
View 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
View 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
View 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
View 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
View 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)

View 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))])
}

View File

@@ -0,0 +1 @@
export ::[,]

107
src/systems/stl/list.orc Normal file
View 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
View 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
View 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
View 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
View 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
View 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)),
]),
)])
}

View 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
View 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))])
}

View 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
View 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)
)

View 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
View 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
View 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)),
]),
)])
}