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:
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