forked from Orchid/orchid
Public API and docs
This commit is contained in:
34
src/stl/assertion_error.rs
Normal file
34
src/stl/assertion_error.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
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 {
|
||||
pub value: ExprInst,
|
||||
pub assertion: &'static str,
|
||||
}
|
||||
|
||||
impl AssertionError {
|
||||
pub fn fail<T>(
|
||||
value: ExprInst,
|
||||
assertion: &'static str,
|
||||
) -> Result<T, Rc<dyn ExternError>> {
|
||||
return Err(Self { value, assertion }.into_extern());
|
||||
}
|
||||
|
||||
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 {}
|
||||
29
src/stl/bool/boolean.rs
Normal file
29
src/stl/bool/boolean.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
use crate::atomic_inert;
|
||||
use crate::foreign::Atom;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::Primitive;
|
||||
|
||||
/// Booleans exposed to Orchid
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Boolean(pub bool);
|
||||
atomic_inert!(Boolean);
|
||||
|
||||
impl From<bool> for Boolean {
|
||||
fn from(value: bool) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ExprInst> for Boolean {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: ExprInst) -> Result<Self, Self::Error> {
|
||||
let expr = value.expr();
|
||||
if let Clause::P(Primitive::Atom(Atom(a))) = &expr.clause {
|
||||
if let Some(b) = a.as_any().downcast_ref::<Boolean>() {
|
||||
return Ok(*b);
|
||||
}
|
||||
}
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
54
src/stl/bool/equals.rs
Normal file
54
src/stl/bool/equals.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::assertion_error::AssertionError;
|
||||
use super::super::litconv::with_lit;
|
||||
use super::boolean::Boolean;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::representations::Literal;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Compares the inner values if
|
||||
///
|
||||
/// - both values are char,
|
||||
/// - both are string,
|
||||
/// - both are either uint or num
|
||||
///
|
||||
/// Next state: [Equals1]
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Equals2;
|
||||
externfn_impl!(Equals2, |_: &Self, x: ExprInst| Ok(Equals1 { x }));
|
||||
|
||||
/// Prev state: [Equals2]; Next state: [Equals0]
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Equals1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Equals1, x);
|
||||
atomic_impl!(Equals1);
|
||||
externfn_impl!(Equals1, |this: &Self, x: ExprInst| {
|
||||
with_lit(&this.x, |l| Ok(Equals0 { a: l.clone(), x }))
|
||||
});
|
||||
|
||||
/// Prev state: [Equals1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Equals0 {
|
||||
a: Literal,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Equals0, x);
|
||||
atomic_impl!(Equals0, |Self { a, x }: &Self, _| {
|
||||
let eqls = with_lit(x, |l| {
|
||||
Ok(match (a, l) {
|
||||
(Literal::Char(c1), Literal::Char(c2)) => c1 == c2,
|
||||
(Literal::Num(n1), Literal::Num(n2)) => n1 == n2,
|
||||
(Literal::Str(s1), Literal::Str(s2)) => s1 == s2,
|
||||
(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(x.clone(), "the expected type")?,
|
||||
})
|
||||
})?;
|
||||
Ok(Boolean::from(eqls).to_atom_cls())
|
||||
});
|
||||
46
src/stl/bool/ifthenelse.rs
Normal file
46
src/stl/bool/ifthenelse.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::super::assertion_error::AssertionError;
|
||||
use super::Boolean;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::PathSet;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Takes a boolean and two branches, runs the first if the bool is true, the
|
||||
/// second if it's false.
|
||||
///
|
||||
/// Next state: [IfThenElse0]
|
||||
#[derive(Clone)]
|
||||
pub struct IfThenElse1;
|
||||
externfn_impl!(IfThenElse1, |_: &Self, x: ExprInst| Ok(IfThenElse0 { x }));
|
||||
|
||||
/// Prev state: [IfThenElse1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IfThenElse0 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(IfThenElse0, x);
|
||||
atomic_impl!(IfThenElse0, |this: &Self, _| {
|
||||
let Boolean(b) = this
|
||||
.x
|
||||
.clone()
|
||||
.try_into()
|
||||
.map_err(|_| AssertionError::ext(this.x.clone(), "a boolean"))?;
|
||||
Ok(if b {
|
||||
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(),
|
||||
}
|
||||
})
|
||||
});
|
||||
16
src/stl/bool/mod.rs
Normal file
16
src/stl/bool/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
mod boolean;
|
||||
mod equals;
|
||||
mod ifthenelse;
|
||||
pub use boolean::Boolean;
|
||||
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
pub fn bool(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("ifthenelse"), ConstTree::xfn(ifthenelse::IfThenElse1)),
|
||||
(i.i("equals"), ConstTree::xfn(equals::Equals2)),
|
||||
(i.i("true"), ConstTree::atom(Boolean(true))),
|
||||
(i.i("false"), ConstTree::atom(Boolean(false))),
|
||||
])
|
||||
}
|
||||
14
src/stl/conv/mod.rs
Normal file
14
src/stl/conv/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
mod parse_float;
|
||||
mod parse_uint;
|
||||
mod to_string;
|
||||
|
||||
pub fn conv(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("parse_float"), ConstTree::xfn(parse_float::ParseFloat1)),
|
||||
(i.i("parse_uint"), ConstTree::xfn(parse_uint::ParseUint1)),
|
||||
(i.i("to_string"), ConstTree::xfn(to_string::ToString1)),
|
||||
])
|
||||
}
|
||||
43
src/stl/conv/parse_float.rs
Normal file
43
src/stl/conv/parse_float.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use chumsky::Parser;
|
||||
|
||||
use super::super::assertion_error::AssertionError;
|
||||
use super::super::litconv::with_lit;
|
||||
use crate::parse::float_parser;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::representations::Literal;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// parse a number. Accepts the same syntax Orchid does
|
||||
///
|
||||
/// Next state: [ParseFloat0]
|
||||
#[derive(Clone)]
|
||||
pub struct ParseFloat1;
|
||||
externfn_impl!(ParseFloat1, |_: &Self, x: ExprInst| Ok(ParseFloat0 { x }));
|
||||
|
||||
/// Prev state: [ParseFloat1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseFloat0 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(ParseFloat0, x);
|
||||
atomic_impl!(ParseFloat0, |Self { x }: &Self, _| {
|
||||
let number = with_lit(x, |l| {
|
||||
Ok(match l {
|
||||
Literal::Str(s) => {
|
||||
let parser = float_parser();
|
||||
parser.parse(s.as_str()).map_err(|_| {
|
||||
AssertionError::ext(x.clone(), "cannot be parsed into a float")
|
||||
})?
|
||||
},
|
||||
Literal::Num(n) => *n,
|
||||
Literal::Uint(i) => (*i as u32).into(),
|
||||
Literal::Char(char) => char
|
||||
.to_digit(10)
|
||||
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))?
|
||||
.into(),
|
||||
})
|
||||
})?;
|
||||
Ok(number.into())
|
||||
});
|
||||
47
src/stl/conv/parse_uint.rs
Normal file
47
src/stl/conv/parse_uint.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use chumsky::Parser;
|
||||
|
||||
use super::super::assertion_error::AssertionError;
|
||||
use super::super::litconv::with_lit;
|
||||
use crate::parse::int_parser;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::representations::Literal;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Parse an unsigned integer. Accepts the same formats Orchid does. If the
|
||||
/// input is a number, floors it.
|
||||
///
|
||||
/// Next state: [ParseUint0]
|
||||
#[derive(Clone)]
|
||||
pub struct ParseUint1;
|
||||
externfn_impl!(ParseUint1, |_: &Self, x: ExprInst| Ok(ParseUint0 { x }));
|
||||
|
||||
/// Prev state: [ParseUint1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ParseUint0 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(ParseUint0, x);
|
||||
atomic_impl!(ParseUint0, |Self { x }: &Self, _| {
|
||||
let uint = with_lit(x, |l| {
|
||||
Ok(match l {
|
||||
Literal::Str(s) => {
|
||||
let parser = int_parser();
|
||||
parser.parse(s.as_str()).map_err(|_| {
|
||||
AssertionError::ext(
|
||||
x.clone(),
|
||||
"cannot be parsed into an unsigned int",
|
||||
)
|
||||
})?
|
||||
},
|
||||
Literal::Num(n) => n.floor() as u64,
|
||||
Literal::Uint(i) => *i,
|
||||
Literal::Char(char) => char
|
||||
.to_digit(10)
|
||||
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))?
|
||||
.into(),
|
||||
})
|
||||
})?;
|
||||
Ok(uint.into())
|
||||
});
|
||||
32
src/stl/conv/to_string.rs
Normal file
32
src/stl/conv/to_string.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::litconv::with_lit;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::representations::Literal;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Convert a literal to a string using Rust's conversions for floats, chars and
|
||||
/// uints respectively
|
||||
///
|
||||
/// Next state: [ToString0]
|
||||
#[derive(Clone)]
|
||||
pub struct ToString1;
|
||||
externfn_impl!(ToString1, |_: &Self, x: ExprInst| Ok(ToString0 { x }));
|
||||
|
||||
/// Prev state: [ToString1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ToString0 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(ToString0, x);
|
||||
atomic_impl!(ToString0, |Self { x }: &Self, _| {
|
||||
let string = with_lit(x, |l| {
|
||||
Ok(match l {
|
||||
Literal::Char(c) => c.to_string(),
|
||||
Literal::Uint(i) => i.to_string(),
|
||||
Literal::Num(n) => n.to_string(),
|
||||
Literal::Str(s) => s.clone(),
|
||||
})
|
||||
})?;
|
||||
Ok(string.into())
|
||||
});
|
||||
32
src/stl/cpsio/debug.rs
Normal file
32
src/stl/cpsio/debug.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
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, externfn_impl};
|
||||
|
||||
/// Print and return whatever expression is in the argument without normalizing
|
||||
/// it.
|
||||
///
|
||||
/// Next state: [Debug1]
|
||||
#[derive(Clone)]
|
||||
pub struct Debug2;
|
||||
externfn_impl!(Debug2, |_: &Self, x: ExprInst| Ok(Debug1 { x }));
|
||||
|
||||
/// Prev state: [Debug2]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Debug1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
impl Atomic for Debug1 {
|
||||
atomic_defaults!();
|
||||
fn run(&self, ctx: Context) -> crate::foreign::AtomicResult {
|
||||
println!("{}", self.x.bundle(ctx.interner));
|
||||
Ok(AtomicReturn {
|
||||
clause: self.x.expr().clause.clone(),
|
||||
gas: ctx.gas.map(|g| g - 1),
|
||||
inert: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
41
src/stl/cpsio/io.rs
Normal file
41
src/stl/cpsio/io.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use std::io::{self, Write};
|
||||
|
||||
use super::super::runtime_error::RuntimeError;
|
||||
use crate::atomic_inert;
|
||||
use crate::interpreter::{HandlerParm, HandlerRes};
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::{Literal, Primitive};
|
||||
use crate::utils::unwrap_or;
|
||||
|
||||
/// An IO command to be handled by the host application.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum IO {
|
||||
Print(String, ExprInst),
|
||||
Readline(ExprInst),
|
||||
}
|
||||
atomic_inert!(IO);
|
||||
|
||||
/// Default xommand handler for IO actions
|
||||
pub fn handle(effect: HandlerParm) -> HandlerRes {
|
||||
// Downcast command
|
||||
let io: &IO = unwrap_or!(effect.as_any().downcast_ref(); Err(effect)?);
|
||||
// Interpret and execute
|
||||
Ok(match io {
|
||||
IO::Print(str, cont) => {
|
||||
print!("{}", str);
|
||||
io::stdout()
|
||||
.flush()
|
||||
.map_err(|e| RuntimeError::ext(e.to_string(), "writing to stdout"))?;
|
||||
cont.clone()
|
||||
},
|
||||
IO::Readline(cont) => {
|
||||
let mut buf = String::new();
|
||||
io::stdin()
|
||||
.read_line(&mut buf)
|
||||
.map_err(|e| RuntimeError::ext(e.to_string(), "reading from stdin"))?;
|
||||
buf.pop();
|
||||
let x = Clause::P(Primitive::Literal(Literal::Str(buf))).wrap();
|
||||
Clause::Apply { f: cont.clone(), x }.wrap()
|
||||
},
|
||||
})
|
||||
}
|
||||
19
src/stl/cpsio/mod.rs
Normal file
19
src/stl/cpsio/mod.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
mod debug;
|
||||
mod io;
|
||||
mod panic;
|
||||
mod print;
|
||||
mod readline;
|
||||
|
||||
pub use io::{handle, IO};
|
||||
|
||||
pub fn cpsio(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("print"), ConstTree::xfn(print::Print2)),
|
||||
(i.i("readline"), ConstTree::xfn(readline::Readln2)),
|
||||
(i.i("debug"), ConstTree::xfn(debug::Debug2)),
|
||||
(i.i("panic"), ConstTree::xfn(panic::Panic1)),
|
||||
])
|
||||
}
|
||||
35
src/stl/cpsio/panic.rs
Normal file
35
src/stl/cpsio/panic.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use super::super::litconv::with_str;
|
||||
use crate::foreign::ExternError;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Takes a message, returns an [ExternError] unconditionally.
|
||||
///
|
||||
/// Next state: [Panic0]
|
||||
#[derive(Clone)]
|
||||
pub struct Panic1;
|
||||
externfn_impl!(Panic1, |_: &Self, x: ExprInst| Ok(Panic0 { x }));
|
||||
|
||||
/// Prev state: [Panic1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Panic0 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Panic0, x);
|
||||
atomic_impl!(Panic0, |Self { x }: &Self, _| {
|
||||
with_str(x, |s| Err(OrchidPanic(s.clone()).into_extern()))
|
||||
});
|
||||
|
||||
/// An unrecoverable error in Orchid land. Of course, because Orchid is lazy, it
|
||||
/// only applies to the expressions that use 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 {}
|
||||
40
src/stl/cpsio/print.rs
Normal file
40
src/stl/cpsio/print.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::litconv::with_str;
|
||||
use super::io::IO;
|
||||
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_defaults, atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Wrap a string and the continuation into an [IO] event to be evaluated by the
|
||||
/// embedder.
|
||||
///
|
||||
/// Next state: [Print1]
|
||||
#[derive(Clone)]
|
||||
pub struct Print2;
|
||||
externfn_impl!(Print2, |_: &Self, x: ExprInst| Ok(Print1 { x }));
|
||||
|
||||
/// Prev state: [Print2]; Next state: [Print0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Print1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Print1, x);
|
||||
atomic_impl!(Print1);
|
||||
externfn_impl!(Print1, |this: &Self, x: ExprInst| {
|
||||
with_str(&this.x, |s| Ok(Print0 { s: s.clone(), x }))
|
||||
});
|
||||
|
||||
/// Prev state: [Print1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Print0 {
|
||||
s: String,
|
||||
x: ExprInst,
|
||||
}
|
||||
impl Atomic for Print0 {
|
||||
atomic_defaults!();
|
||||
fn run(&self, ctx: Context) -> AtomicResult {
|
||||
Ok(AtomicReturn::from_data(IO::Print(self.s.clone(), self.x.clone()), ctx))
|
||||
}
|
||||
}
|
||||
27
src/stl/cpsio/readline.rs
Normal file
27
src/stl/cpsio/readline.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::io::IO;
|
||||
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_defaults, externfn_impl};
|
||||
|
||||
/// Create an [IO] event that reads a line form standard input and calls the
|
||||
/// continuation with it.
|
||||
///
|
||||
/// Next state: [Readln1]
|
||||
#[derive(Clone)]
|
||||
pub struct Readln2;
|
||||
externfn_impl!(Readln2, |_: &Self, x: ExprInst| Ok(Readln1 { x }));
|
||||
|
||||
/// Prev state: [Readln2]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Readln1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
impl Atomic for Readln1 {
|
||||
atomic_defaults!();
|
||||
fn run(&self, ctx: Context) -> AtomicResult {
|
||||
Ok(AtomicReturn::from_data(IO::Readline(self.x.clone()), ctx))
|
||||
}
|
||||
}
|
||||
45
src/stl/litconv.rs
Normal file
45
src/stl/litconv.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::assertion_error::AssertionError;
|
||||
use crate::foreign::ExternError;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::representations::Literal;
|
||||
|
||||
/// 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")?
|
||||
}
|
||||
})
|
||||
}
|
||||
11
src/stl/mk_stl.rs
Normal file
11
src/stl/mk_stl.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use super::bool::bool;
|
||||
use super::conv::conv;
|
||||
use super::cpsio::cpsio;
|
||||
use super::num::num;
|
||||
use super::str::str;
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
pub fn mk_stl(i: &Interner) -> ConstTree {
|
||||
cpsio(i) + conv(i) + bool(i) + str(i) + num(i)
|
||||
}
|
||||
12
src/stl/mod.rs
Normal file
12
src/stl/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
mod assertion_error;
|
||||
mod bool;
|
||||
mod conv;
|
||||
mod cpsio;
|
||||
mod litconv;
|
||||
mod mk_stl;
|
||||
mod num;
|
||||
mod runtime_error;
|
||||
mod str;
|
||||
|
||||
pub use cpsio::{handle as handleIO, IO};
|
||||
pub use mk_stl::mk_stl;
|
||||
16
src/stl/num/mod.rs
Normal file
16
src/stl/num/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
mod numeric;
|
||||
pub mod operators;
|
||||
pub use numeric::Numeric;
|
||||
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
pub fn num(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([
|
||||
(i.i("add"), ConstTree::xfn(operators::add::Add2)),
|
||||
(i.i("subtract"), ConstTree::xfn(operators::subtract::Subtract2)),
|
||||
(i.i("multiply"), ConstTree::xfn(operators::multiply::Multiply2)),
|
||||
(i.i("divide"), ConstTree::xfn(operators::divide::Divide2)),
|
||||
(i.i("remainder"), ConstTree::xfn(operators::remainder::Remainder2)),
|
||||
])
|
||||
}
|
||||
133
src/stl/num/numeric.rs
Normal file
133
src/stl/num/numeric.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
use std::ops::{Add, Div, Mul, Rem, Sub};
|
||||
use std::rc::Rc;
|
||||
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::super::assertion_error::AssertionError;
|
||||
use super::super::litconv::with_lit;
|
||||
use crate::foreign::ExternError;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::{Literal, Primitive};
|
||||
|
||||
/// A number, either floating point or unsigned int, visible to Orchid.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Numeric {
|
||||
Uint(u64),
|
||||
Num(NotNan<f64>),
|
||||
}
|
||||
|
||||
impl Numeric {
|
||||
/// Wrap a f64 in a Numeric
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if the value is NaN or Infinity.try_into()
|
||||
fn num<T: Into<f64>>(value: T) -> Self {
|
||||
let f = value.into();
|
||||
assert!(f.is_finite(), "unrepresentable number");
|
||||
NotNan::try_from(f).map(Self::Num).expect("not a number")
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Numeric {
|
||||
type Output = Numeric;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a + b),
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a + b),
|
||||
(Numeric::Uint(a), Numeric::Num(b))
|
||||
| (Numeric::Num(b), Numeric::Uint(a)) =>
|
||||
Numeric::num::<f64>(a as f64 + *b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Numeric {
|
||||
type Output = Numeric;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) if b <= a => Numeric::Uint(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::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 - *b),
|
||||
(Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a - b as f64),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for Numeric {
|
||||
type Output = Numeric;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a * b),
|
||||
(Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a * b),
|
||||
(Numeric::Uint(a), Numeric::Num(b))
|
||||
| (Numeric::Num(b), Numeric::Uint(a)) =>
|
||||
Numeric::Num(NotNan::new(a as f64).unwrap() * b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for Numeric {
|
||||
type Output = Numeric;
|
||||
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
let a: f64 = self.into();
|
||||
let b: f64 = rhs.into();
|
||||
Numeric::num(a / b)
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem for Numeric {
|
||||
type Output = Numeric;
|
||||
|
||||
fn rem(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a % b),
|
||||
(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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ExprInst> for Numeric {
|
||||
type Error = Rc<dyn ExternError>;
|
||||
fn try_from(value: ExprInst) -> Result<Self, Self::Error> {
|
||||
with_lit(&value.clone(), |l| match l {
|
||||
Literal::Uint(i) => Ok(Numeric::Uint(*i)),
|
||||
Literal::Num(n) => Ok(Numeric::Num(*n)),
|
||||
_ => AssertionError::fail(value, "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),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Numeric> for String {
|
||||
fn from(value: Numeric) -> Self {
|
||||
match value {
|
||||
Numeric::Uint(i) => i.to_string(),
|
||||
Numeric::Num(n) => n.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Numeric> for f64 {
|
||||
fn from(val: Numeric) -> Self {
|
||||
match val {
|
||||
Numeric::Num(n) => *n,
|
||||
Numeric::Uint(i) => i as f64,
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/stl/num/operators/add.rs
Normal file
36
src/stl/num/operators/add.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::Numeric;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Adds two numbers
|
||||
///
|
||||
/// Next state: [Add1]
|
||||
#[derive(Clone)]
|
||||
pub struct Add2;
|
||||
externfn_impl!(Add2, |_: &Self, x: ExprInst| Ok(Add1 { x }));
|
||||
|
||||
/// Prev state: [Add2]; Next state: [Add0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Add1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Add1, x);
|
||||
atomic_impl!(Add1);
|
||||
externfn_impl!(Add1, |this: &Self, x: ExprInst| {
|
||||
let a: Numeric = this.x.clone().try_into()?;
|
||||
Ok(Add0 { a, x })
|
||||
});
|
||||
|
||||
/// Prev state: [Add1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Add0 {
|
||||
a: Numeric,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Add0, x);
|
||||
atomic_impl!(Add0, |Self { a, x }: &Self, _| {
|
||||
let b: Numeric = x.clone().try_into()?;
|
||||
Ok((*a + b).into())
|
||||
});
|
||||
37
src/stl/num/operators/divide.rs
Normal file
37
src/stl/num/operators/divide.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::Numeric;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Divides two numbers
|
||||
///
|
||||
/// Next state: [Divide1]
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Divide2;
|
||||
externfn_impl!(Divide2, |_: &Self, x: ExprInst| Ok(Divide1 { x }));
|
||||
|
||||
/// Prev state: [Divide2]; Next state: [Divide0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Divide1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Divide1, x);
|
||||
atomic_impl!(Divide1);
|
||||
externfn_impl!(Divide1, |this: &Self, x: ExprInst| {
|
||||
let a: Numeric = this.x.clone().try_into()?;
|
||||
Ok(Divide0 { a, x })
|
||||
});
|
||||
|
||||
/// Prev state: [Divide1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Divide0 {
|
||||
a: Numeric,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Divide0, x);
|
||||
atomic_impl!(Divide0, |Self { a, x }: &Self, _| {
|
||||
let b: Numeric = x.clone().try_into()?;
|
||||
Ok((*a / b).into())
|
||||
});
|
||||
5
src/stl/num/operators/mod.rs
Normal file
5
src/stl/num/operators/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod add;
|
||||
pub mod divide;
|
||||
pub mod multiply;
|
||||
pub mod remainder;
|
||||
pub mod subtract;
|
||||
36
src/stl/num/operators/multiply.rs
Normal file
36
src/stl/num/operators/multiply.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::Numeric;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Multiplies two numbers
|
||||
///
|
||||
/// Next state: [Multiply1]
|
||||
#[derive(Clone)]
|
||||
pub struct Multiply2;
|
||||
externfn_impl!(Multiply2, |_: &Self, x: ExprInst| Ok(Multiply1 { x }));
|
||||
|
||||
/// Prev state: [Multiply2]; Next state: [Multiply0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Multiply1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Multiply1, x);
|
||||
atomic_impl!(Multiply1);
|
||||
externfn_impl!(Multiply1, |this: &Self, x: ExprInst| {
|
||||
let a: Numeric = this.x.clone().try_into()?;
|
||||
Ok(Multiply0 { a, x })
|
||||
});
|
||||
|
||||
/// Prev state: [Multiply1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Multiply0 {
|
||||
a: Numeric,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Multiply0, x);
|
||||
atomic_impl!(Multiply0, |Self { a, x }: &Self, _| {
|
||||
let b: Numeric = x.clone().try_into()?;
|
||||
Ok((*a * b).into())
|
||||
});
|
||||
36
src/stl/num/operators/remainder.rs
Normal file
36
src/stl/num/operators/remainder.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::Numeric;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Takes the modulus of two numbers.
|
||||
///
|
||||
/// Next state: [Remainder1]
|
||||
#[derive(Clone)]
|
||||
pub struct Remainder2;
|
||||
externfn_impl!(Remainder2, |_: &Self, x: ExprInst| Ok(Remainder1 { x }));
|
||||
|
||||
/// Prev state: [Remainder2]; Next state: [Remainder0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Remainder1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Remainder1, x);
|
||||
atomic_impl!(Remainder1);
|
||||
externfn_impl!(Remainder1, |this: &Self, x: ExprInst| {
|
||||
let a: Numeric = this.x.clone().try_into()?;
|
||||
Ok(Remainder0 { a, x })
|
||||
});
|
||||
|
||||
/// Prev state: [Remainder1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Remainder0 {
|
||||
a: Numeric,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Remainder0, x);
|
||||
atomic_impl!(Remainder0, |Self { a, x }: &Self, _| {
|
||||
let b: Numeric = x.clone().try_into()?;
|
||||
Ok((*a % b).into())
|
||||
});
|
||||
36
src/stl/num/operators/subtract.rs
Normal file
36
src/stl/num/operators/subtract.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::Numeric;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Subtracts two numbers
|
||||
///
|
||||
/// Next state: [Subtract1]
|
||||
#[derive(Clone)]
|
||||
pub struct Subtract2;
|
||||
externfn_impl!(Subtract2, |_: &Self, x: ExprInst| Ok(Subtract1 { x }));
|
||||
|
||||
/// Prev state: [Subtract2]; Next state: [Subtract0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Subtract1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Subtract1, x);
|
||||
atomic_impl!(Subtract1);
|
||||
externfn_impl!(Subtract1, |this: &Self, x: ExprInst| {
|
||||
let a: Numeric = this.x.clone().try_into()?;
|
||||
Ok(Subtract0 { a, x })
|
||||
});
|
||||
|
||||
/// Prev state: [Subtract1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Subtract0 {
|
||||
a: Numeric,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Subtract0, x);
|
||||
atomic_impl!(Subtract0, |Self { a, x }: &Self, _| {
|
||||
let b: Numeric = x.clone().try_into()?;
|
||||
Ok((*a - b).into())
|
||||
});
|
||||
32
src/stl/runtime_error.rs
Normal file
32
src/stl/runtime_error.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
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 {
|
||||
pub fn fail<T>(
|
||||
message: String,
|
||||
operation: &'static str,
|
||||
) -> Result<T, Rc<dyn ExternError>> {
|
||||
return Err(Self { message, operation }.into_extern());
|
||||
}
|
||||
|
||||
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 {}
|
||||
45
src/stl/str/char_at.rs
Normal file
45
src/stl/str/char_at.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::litconv::{with_str, with_uint};
|
||||
use super::super::runtime_error::RuntimeError;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::{Literal, Primitive};
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Takes an uint and a string, finds the char in a string at a 0-based index
|
||||
///
|
||||
/// Next state: [CharAt1]
|
||||
#[derive(Clone)]
|
||||
pub struct CharAt2;
|
||||
externfn_impl!(CharAt2, |_: &Self, x: ExprInst| Ok(CharAt1 { x }));
|
||||
|
||||
/// Prev state: [CharAt2]; Next state: [CharAt0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CharAt1 {
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(CharAt1, x);
|
||||
atomic_impl!(CharAt1);
|
||||
externfn_impl!(CharAt1, |this: &Self, x: ExprInst| {
|
||||
with_str(&this.x, |s| Ok(CharAt0 { s: s.clone(), x }))
|
||||
});
|
||||
|
||||
/// Prev state: [CharAt1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CharAt0 {
|
||||
s: String,
|
||||
x: ExprInst,
|
||||
}
|
||||
atomic_redirect!(CharAt0, x);
|
||||
atomic_impl!(CharAt0, |Self { s, x }: &Self, _| {
|
||||
with_uint(x, |i| {
|
||||
if let Some(c) = s.chars().nth(i as usize) {
|
||||
Ok(Clause::P(Primitive::Literal(Literal::Char(c))))
|
||||
} else {
|
||||
RuntimeError::fail(
|
||||
"Character index out of bounds".to_string(),
|
||||
"indexing string",
|
||||
)?
|
||||
}
|
||||
})
|
||||
});
|
||||
37
src/stl/str/concatenate.rs
Normal file
37
src/stl/str/concatenate.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::super::litconv::with_str;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::{Literal, Primitive};
|
||||
use crate::{atomic_impl, atomic_redirect, externfn_impl};
|
||||
|
||||
/// Concatenates two strings
|
||||
///
|
||||
/// Next state: [Concatenate1]
|
||||
#[derive(Clone)]
|
||||
pub struct Concatenate2;
|
||||
externfn_impl!(Concatenate2, |_: &Self, c: ExprInst| Ok(Concatenate1 { c }));
|
||||
|
||||
/// Prev state: [Concatenate2]; Next state: [Concatenate0]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Concatenate1 {
|
||||
c: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Concatenate1, c);
|
||||
atomic_impl!(Concatenate1);
|
||||
externfn_impl!(Concatenate1, |this: &Self, c: ExprInst| {
|
||||
with_str(&this.c, |a| Ok(Concatenate0 { a: a.clone(), c }))
|
||||
});
|
||||
|
||||
/// Prev state: [Concatenate1]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Concatenate0 {
|
||||
a: String,
|
||||
c: ExprInst,
|
||||
}
|
||||
atomic_redirect!(Concatenate0, c);
|
||||
atomic_impl!(Concatenate0, |Self { a, c }: &Self, _| {
|
||||
with_str(c, |b| {
|
||||
Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + b))))
|
||||
})
|
||||
});
|
||||
12
src/stl/str/mod.rs
Normal file
12
src/stl/str/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
mod char_at;
|
||||
mod concatenate;
|
||||
|
||||
use crate::interner::Interner;
|
||||
use crate::pipeline::ConstTree;
|
||||
|
||||
pub fn str(i: &Interner) -> ConstTree {
|
||||
ConstTree::tree([(
|
||||
i.i("concatenate"),
|
||||
ConstTree::xfn(concatenate::Concatenate2),
|
||||
)])
|
||||
}
|
||||
Reference in New Issue
Block a user