forked from Orchid/orchid
Basic string and binary processing
- strings are now made of graphemes - char is no longer a literal type - preliminary binary support - added implicit extraction methods for primitives - added explicit extraction method for atoms Nothing has been tested yet
This commit is contained in:
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -412,8 +412,10 @@ dependencies = [
|
|||||||
"ordered-float",
|
"ordered-float",
|
||||||
"paste",
|
"paste",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
|
"take_mut",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"trait-set",
|
"trait-set",
|
||||||
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -615,6 +617,12 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "take_mut"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.40"
|
version = "1.0.40"
|
||||||
@@ -658,6 +666,12 @@ version = "1.0.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|||||||
@@ -33,3 +33,5 @@ trait-set = "0.3"
|
|||||||
paste = "1.0"
|
paste = "1.0"
|
||||||
rust-embed = { version = "6.6", features = ["include-exclude"] }
|
rust-embed = { version = "6.6", features = ["include-exclude"] }
|
||||||
duplicate = "1.0.0"
|
duplicate = "1.0.0"
|
||||||
|
take_mut = "0.2.2"
|
||||||
|
unicode-segmentation = "1.10.1"
|
||||||
|
|||||||
5
ROADMAP.md
Normal file
5
ROADMAP.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# IO
|
||||||
|
|
||||||
|
All IO is event-based via callbacks.
|
||||||
|
Driven streams such as stdin expose single-fire events for the results of functions such as "read until terminator" or "read N bytes".
|
||||||
|
Network IO exposes repeated events such as "connect", "message", etc.
|
||||||
@@ -63,7 +63,6 @@ use crate::Primitive;
|
|||||||
/// atomic_redirect!(InternalToString, expr_inst);
|
/// atomic_redirect!(InternalToString, expr_inst);
|
||||||
/// atomic_impl!(InternalToString, |Self { expr_inst }: &Self, _|{
|
/// atomic_impl!(InternalToString, |Self { expr_inst }: &Self, _|{
|
||||||
/// with_lit(expr_inst, |l| Ok(match l {
|
/// with_lit(expr_inst, |l| Ok(match l {
|
||||||
/// Literal::Char(c) => c.to_string(),
|
|
||||||
/// Literal::Uint(i) => i.to_string(),
|
/// Literal::Uint(i) => i.to_string(),
|
||||||
/// Literal::Num(n) => n.to_string(),
|
/// Literal::Num(n) => n.to_string(),
|
||||||
/// Literal::Str(s) => s.clone(),
|
/// Literal::Str(s) => s.clone(),
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ use crate::write_fn_step;
|
|||||||
/// /// Convert a literal to a string using Rust's conversions for floats,
|
/// /// Convert a literal to a string using Rust's conversions for floats,
|
||||||
/// /// chars and uints respectively
|
/// /// chars and uints respectively
|
||||||
/// ToString = |x| with_lit(x, |l| Ok(match l {
|
/// ToString = |x| with_lit(x, |l| Ok(match l {
|
||||||
/// Literal::Char(c) => c.to_string(),
|
|
||||||
/// Literal::Uint(i) => i.to_string(),
|
/// Literal::Uint(i) => i.to_string(),
|
||||||
/// Literal::Num(n) => n.to_string(),
|
/// Literal::Num(n) => n.to_string(),
|
||||||
/// Literal::Str(s) => s.clone(),
|
/// Literal::Str(s) => s.clone(),
|
||||||
@@ -147,12 +146,12 @@ macro_rules! define_fn {
|
|||||||
$crate::write_fn_step!(
|
$crate::write_fn_step!(
|
||||||
$name
|
$name
|
||||||
{
|
{
|
||||||
$( $arg_prev:ident : $typ_prev:ty ),*
|
$( $arg_prev : $typ_prev ),*
|
||||||
}
|
}
|
||||||
[< $name $arg0:upper >]
|
[< $name $arg0:camel >]
|
||||||
where $arg0:$typ0 $( = $xname => $parse0 )? ;
|
where $arg0:$typ0 $( = $xname => $parse0 )? ;
|
||||||
);
|
);
|
||||||
$crate::define_fn!(@MIDDLE $xname [< $name $arg0:upper >] ($body)
|
$crate::define_fn!(@MIDDLE $xname [< $name $arg0:camel >] ($body)
|
||||||
(
|
(
|
||||||
$( ($arg_prev: $typ_prev) )*
|
$( ($arg_prev: $typ_prev) )*
|
||||||
($arg0: $typ0)
|
($arg0: $typ0)
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ use crate::interpreted::ExprInst;
|
|||||||
/// discussed below. The newly bound names (here `s` and `i` before `=`) can
|
/// discussed below. The newly bound names (here `s` and `i` before `=`) can
|
||||||
/// also receive type annotations.
|
/// also receive type annotations.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```no_run
|
||||||
|
/// // FIXME this is a very old example that wouldn't compile now
|
||||||
|
/// use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
///
|
||||||
/// use orchidlang::{write_fn_step, Literal, Primitive};
|
/// use orchidlang::{write_fn_step, Literal, Primitive};
|
||||||
/// use orchidlang::interpreted::Clause;
|
/// use orchidlang::interpreted::Clause;
|
||||||
/// use orchidlang::stl::litconv::{with_str, with_uint};
|
/// use orchidlang::stl::litconv::{with_str, with_uint};
|
||||||
@@ -36,8 +39,8 @@ use crate::interpreted::ExprInst;
|
|||||||
/// CharAt0 { s: String }
|
/// CharAt0 { s: String }
|
||||||
/// i = x => with_uint(x, Ok);
|
/// i = x => with_uint(x, Ok);
|
||||||
/// {
|
/// {
|
||||||
/// if let Some(c) = s.chars().nth(*i as usize) {
|
/// if let Some(c) = s.graphemes(true).nth(*i as usize) {
|
||||||
/// Ok(Clause::P(Primitive::Literal(Literal::Char(c))))
|
/// Ok(Literal::Char(c.to_string()).into())
|
||||||
/// } else {
|
/// } else {
|
||||||
/// RuntimeError::fail(
|
/// RuntimeError::fail(
|
||||||
/// "Character index out of bounds".to_string(),
|
/// "Character index out of bounds".to_string(),
|
||||||
|
|||||||
@@ -161,7 +161,6 @@ pub fn literal_parser() -> impl SimpleParser<char, Literal> {
|
|||||||
// all ints are valid floats so it takes precedence
|
// all ints are valid floats so it takes precedence
|
||||||
number::int_parser().map(Literal::Uint),
|
number::int_parser().map(Literal::Uint),
|
||||||
number::float_parser().map(Literal::Num),
|
number::float_parser().map(Literal::Num),
|
||||||
string::char_parser().map(Literal::Char),
|
|
||||||
string::str_parser().map(Literal::Str),
|
string::str_parser().map(Literal::Str),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ fn op_parser<'a>(
|
|||||||
pub static NOT_NAME_CHAR: &[char] = &[
|
pub static NOT_NAME_CHAR: &[char] = &[
|
||||||
':', // used for namespacing and type annotations
|
':', // used for namespacing and type annotations
|
||||||
'\\', '@', // parametric expression starters
|
'\\', '@', // parametric expression starters
|
||||||
'"', '\'', // parsed as primitives and therefore would never match
|
'"', // parsed as primitive and therefore would never match
|
||||||
'(', ')', '[', ']', '{', '}', // must be strictly balanced
|
'(', ')', '[', ']', '{', '}', // must be strictly balanced
|
||||||
'.', // Argument-body separator in parametrics
|
'.', // Argument-body separator in parametrics
|
||||||
',', // used in imports
|
',', // used in imports
|
||||||
|
|||||||
@@ -34,17 +34,12 @@ fn text_parser(delim: char) -> impl SimpleParser<char, char> {
|
|||||||
filter(move |&c| c != '\\' && c != delim).or(escape)
|
filter(move |&c| c != '\\' && c != delim).or(escape)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a character literal between single quotes
|
|
||||||
pub fn char_parser() -> impl SimpleParser<char, char> {
|
|
||||||
just('\'').ignore_then(text_parser('\'')).then_ignore(just('\''))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a string between double quotes
|
/// Parse a string between double quotes
|
||||||
pub fn str_parser() -> impl SimpleParser<char, String> {
|
pub fn str_parser() -> impl SimpleParser<char, String> {
|
||||||
just('"')
|
just('"')
|
||||||
.ignore_then(
|
.ignore_then(
|
||||||
text_parser('"').map(Some)
|
text_parser('"').map(Some)
|
||||||
.or(just("\\\n").map(|_| None)) // Newlines preceded by backslashes are ignored.
|
.or(just("\\\n").then(just(' ').or(just('\t')).repeated()).map(|_| None)) // Newlines preceded by backslashes are ignored along with all following indentation.
|
||||||
.repeated(),
|
.repeated(),
|
||||||
)
|
)
|
||||||
.then_ignore(just('"'))
|
.then_ignore(just('"'))
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ pub enum Literal {
|
|||||||
Num(NotNan<f64>),
|
Num(NotNan<f64>),
|
||||||
/// An unsigned integer; a size, index or pointer
|
/// An unsigned integer; a size, index or pointer
|
||||||
Uint(u64),
|
Uint(u64),
|
||||||
/// A single utf-8 codepoint
|
|
||||||
Char(char),
|
|
||||||
/// A utf-8 character sequence
|
/// A utf-8 character sequence
|
||||||
Str(String),
|
Str(String),
|
||||||
}
|
}
|
||||||
@@ -21,7 +19,6 @@ impl Debug for Literal {
|
|||||||
match self {
|
match self {
|
||||||
Self::Num(arg0) => write!(f, "{:?}", arg0),
|
Self::Num(arg0) => write!(f, "{:?}", arg0),
|
||||||
Self::Uint(arg0) => write!(f, "{:?}", arg0),
|
Self::Uint(arg0) => write!(f, "{:?}", arg0),
|
||||||
Self::Char(arg0) => write!(f, "{:?}", arg0),
|
|
||||||
Self::Str(arg0) => write!(f, "{:?}", arg0),
|
Self::Str(arg0) => write!(f, "{:?}", arg0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,11 +34,6 @@ impl From<u64> for Literal {
|
|||||||
Self::Uint(value)
|
Self::Uint(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<char> for Literal {
|
|
||||||
fn from(value: char) -> Self {
|
|
||||||
Self::Char(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<String> for Literal {
|
impl From<String> for Literal {
|
||||||
fn from(value: String) -> Self {
|
fn from(value: String) -> Self {
|
||||||
Self::Str(value)
|
Self::Str(value)
|
||||||
|
|||||||
171
src/stl/bin.rs
Normal file
171
src/stl/bin.rs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
use std::fmt::Debug;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use super::codegen::{orchid_opt, tuple};
|
||||||
|
use super::inspect::{with_atom, with_uint};
|
||||||
|
use super::{RuntimeError, Boolean};
|
||||||
|
use crate::foreign::ExternError;
|
||||||
|
use crate::interpreted::ExprInst;
|
||||||
|
use crate::utils::{iter_find, unwrap_or};
|
||||||
|
use crate::{atomic_inert, define_fn, ConstTree, Interner, Literal};
|
||||||
|
|
||||||
|
/// A block of binary data
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Binary(pub Rc<Vec<u8>>);
|
||||||
|
atomic_inert!(Binary);
|
||||||
|
|
||||||
|
impl TryFrom<&ExprInst> for Binary {
|
||||||
|
type Error = Rc<dyn ExternError>;
|
||||||
|
|
||||||
|
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||||
|
with_atom(value, "a blob", |a: &Binary| Ok(a.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(Rc::new(bytes)).to_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(Rc::new(data)).to_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(Rc::new(data)).to_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(Rc::new(asl.to_vec())).to_atom_cls().into(),
|
||||||
|
Binary(Rc::new(bsl.to_vec())).to_atom_cls().into(),
|
||||||
|
]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define_fn! {
|
||||||
|
/// Detect the number of bytes in the binary data block
|
||||||
|
pub Size = |x: &ExprInst| {
|
||||||
|
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))
|
||||||
|
]),
|
||||||
|
)])
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::foreign::Atom;
|
use crate::foreign::ExternError;
|
||||||
use crate::interner::Interner;
|
use crate::interner::Interner;
|
||||||
use crate::representations::interpreted::{Clause, ExprInst};
|
use crate::representations::interpreted::{Clause, ExprInst};
|
||||||
use crate::representations::Primitive;
|
|
||||||
use crate::stl::litconv::with_lit;
|
|
||||||
use crate::stl::AssertionError;
|
use crate::stl::AssertionError;
|
||||||
use crate::{atomic_inert, define_fn, ConstTree, Literal, PathSet};
|
use crate::{atomic_inert, define_fn, ConstTree, Literal, PathSet};
|
||||||
|
|
||||||
|
use super::inspect::with_atom;
|
||||||
|
|
||||||
/// Booleans exposed to Orchid
|
/// Booleans exposed to Orchid
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct Boolean(pub bool);
|
pub struct Boolean(pub bool);
|
||||||
@@ -20,32 +20,21 @@ impl From<bool> for Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&ExprInst> for Boolean {
|
impl TryFrom<&ExprInst> for Boolean {
|
||||||
type Error = ();
|
type Error = Rc<dyn ExternError>;
|
||||||
|
|
||||||
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||||
let expr = value.expr();
|
with_atom(value, "a boolean", |b| Ok(*b))
|
||||||
if let Clause::P(Primitive::Atom(Atom(a))) = &expr.clause {
|
|
||||||
if let Some(b) = a.as_any().downcast_ref::<Boolean>() {
|
|
||||||
return Ok(*b);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
define_fn! {expr=x in
|
define_fn! {expr=x in
|
||||||
/// Compares the inner values if
|
/// Compares the inner values if
|
||||||
///
|
///
|
||||||
/// - both values are char,
|
|
||||||
/// - both are string,
|
/// - both are string,
|
||||||
/// - both are either uint or num
|
/// - both are either uint or num
|
||||||
Equals {
|
Equals { a: Literal, b: Literal } => Ok(Boolean::from(match (a, b) {
|
||||||
a: Literal as with_lit(x, |l| Ok(l.clone())),
|
|
||||||
b: Literal as with_lit(x, |l| Ok(l.clone()))
|
|
||||||
} => Ok(Boolean::from(match (a, b) {
|
|
||||||
(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::Str(s1), Literal::Str(s2)) => s1 == s2,
|
||||||
|
(Literal::Num(n1), Literal::Num(n2)) => n1 == n2,
|
||||||
(Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2,
|
(Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2,
|
||||||
(Literal::Num(n1), Literal::Uint(u1)) => *n1 == (*u1 as f64),
|
(Literal::Num(n1), Literal::Uint(u1)) => *n1 == (*u1 as f64),
|
||||||
(Literal::Uint(u1), Literal::Num(n1)) => *n1 == (*u1 as f64),
|
(Literal::Uint(u1), Literal::Num(n1)) => *n1 == (*u1 as f64),
|
||||||
|
|||||||
46
src/stl/codegen.rs
Normal file
46
src/stl/codegen.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
//! Utilities for generating Orchid code in Rust
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::interpreted::{Clause, ExprInst};
|
||||||
|
use crate::{PathSet, Side};
|
||||||
|
|
||||||
|
/// Convert a rust Option into an Orchid Option
|
||||||
|
pub fn orchid_opt(x: Option<ExprInst>) -> Clause {
|
||||||
|
if let Some(x) = x { some(x) } else { none() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs an instance of the orchid value Some wrapping the given
|
||||||
|
/// [ExprInst]
|
||||||
|
fn some(x: ExprInst) -> Clause {
|
||||||
|
Clause::Lambda {
|
||||||
|
args: None,
|
||||||
|
body: Clause::Lambda {
|
||||||
|
args: Some(PathSet { steps: Rc::new(vec![Side::Left]), next: None }),
|
||||||
|
body: Clause::Apply { f: Clause::LambdaArg.wrap(), x }.wrap(),
|
||||||
|
}
|
||||||
|
.wrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constructs an instance of the orchid value None
|
||||||
|
fn none() -> Clause {
|
||||||
|
Clause::Lambda {
|
||||||
|
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
|
||||||
|
body: Clause::Lambda { args: None, body: Clause::LambdaArg.wrap() }.wrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define a clause that can be called with a callback and passes the provided
|
||||||
|
/// values to the callback in order.
|
||||||
|
pub fn tuple(data: Vec<ExprInst>) -> Clause {
|
||||||
|
Clause::Lambda {
|
||||||
|
args: Some(PathSet {
|
||||||
|
next: None,
|
||||||
|
steps: Rc::new(data.iter().map(|_| Side::Left).collect()),
|
||||||
|
}),
|
||||||
|
body: data
|
||||||
|
.into_iter()
|
||||||
|
.fold(Clause::LambdaArg.wrap(), |f, x| Clause::Apply { f, x }.wrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use chumsky::Parser;
|
use chumsky::Parser;
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
use super::litconv::with_lit;
|
use super::inspect::with_lit;
|
||||||
use super::{ArithmeticError, AssertionError};
|
use super::{ArithmeticError, AssertionError};
|
||||||
use crate::foreign::ExternError;
|
use crate::foreign::ExternError;
|
||||||
use crate::interner::Interner;
|
use crate::interner::Interner;
|
||||||
@@ -20,10 +20,6 @@ define_fn! {
|
|||||||
Literal::Num(n) => Ok(*n),
|
Literal::Num(n) => Ok(*n),
|
||||||
Literal::Uint(i) => NotNan::new(*i as f64)
|
Literal::Uint(i) => NotNan::new(*i as f64)
|
||||||
.map_err(|_| ArithmeticError::NaN.into_extern()),
|
.map_err(|_| ArithmeticError::NaN.into_extern()),
|
||||||
Literal::Char(char) => char
|
|
||||||
.to_digit(10)
|
|
||||||
.map(|i| NotNan::new(i as f64).expect("u32 to f64 yielded NaN"))
|
|
||||||
.ok_or_else(|| AssertionError::ext(x.clone(), "is not a decimal digit")),
|
|
||||||
}).map(|nn| Literal::Num(nn).into())
|
}).map(|nn| Literal::Num(nn).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,10 +35,6 @@ define_fn! {
|
|||||||
)),
|
)),
|
||||||
Literal::Num(n) => Ok(n.floor() as u64),
|
Literal::Num(n) => Ok(n.floor() as u64),
|
||||||
Literal::Uint(i) => Ok(*i),
|
Literal::Uint(i) => Ok(*i),
|
||||||
Literal::Char(char) => char
|
|
||||||
.to_digit(10)
|
|
||||||
.map(u64::from)
|
|
||||||
.ok_or(AssertionError::ext(x.clone(), "is not a decimal digit")),
|
|
||||||
}).map(|u| Literal::Uint(u).into())
|
}).map(|u| Literal::Uint(u).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +42,6 @@ define_fn! {
|
|||||||
/// Convert a literal to a string using Rust's conversions for floats, chars and
|
/// Convert a literal to a string using Rust's conversions for floats, chars and
|
||||||
/// uints respectively
|
/// uints respectively
|
||||||
ToString = |x| with_lit(x, |l| Ok(match l {
|
ToString = |x| with_lit(x, |l| Ok(match l {
|
||||||
Literal::Char(c) => c.to_string(),
|
|
||||||
Literal::Uint(i) => i.to_string(),
|
Literal::Uint(i) => i.to_string(),
|
||||||
Literal::Num(n) => n.to_string(),
|
Literal::Num(n) => n.to_string(),
|
||||||
Literal::Str(s) => s.clone(),
|
Literal::Str(s) => s.clone(),
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::assertion_error::AssertionError;
|
use super::assertion_error::AssertionError;
|
||||||
use crate::foreign::ExternError;
|
use crate::foreign::{ExternError, Atomic};
|
||||||
|
use crate::interpreted::Clause;
|
||||||
use crate::representations::interpreted::ExprInst;
|
use crate::representations::interpreted::ExprInst;
|
||||||
use crate::representations::Literal;
|
use crate::representations::Literal;
|
||||||
|
use crate::Primitive;
|
||||||
|
|
||||||
/// Tries to cast the [ExprInst] as a [Literal], calls the provided function on
|
/// Tries to cast the [ExprInst] as a [Literal], calls the provided function on
|
||||||
/// it if successful. Returns a generic [AssertionError] if not.
|
/// it if successful. Returns a generic [AssertionError] if not.
|
||||||
@@ -45,3 +47,48 @@ pub fn with_uint<T>(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tries to cast the [ExprInst] into the specified atom type. Throws an
|
||||||
|
/// assertion error if unsuccessful, or calls the provided function on the
|
||||||
|
/// extracted atomic type.
|
||||||
|
pub fn with_atom<T: Atomic, U>(
|
||||||
|
x: &ExprInst,
|
||||||
|
inexact_typename: &'static str,
|
||||||
|
predicate: impl FnOnce(&T) -> Result<U, Rc<dyn ExternError>>,
|
||||||
|
) -> Result<U, Rc<dyn ExternError>> {
|
||||||
|
x.inspect(|c| {
|
||||||
|
if let Clause::P(Primitive::Atom(a)) = c {
|
||||||
|
a.try_cast()
|
||||||
|
.map(predicate)
|
||||||
|
.unwrap_or_else(|| AssertionError::fail(x.clone(), inexact_typename))
|
||||||
|
} else {
|
||||||
|
AssertionError::fail(x.clone(), "an atom")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ######## Automatically ########
|
||||||
|
|
||||||
|
impl TryFrom<&ExprInst> for Literal {
|
||||||
|
type Error = Rc<dyn ExternError>;
|
||||||
|
|
||||||
|
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||||
|
with_lit(value, |l| Ok(l.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&ExprInst> for String {
|
||||||
|
type Error = Rc<dyn ExternError>;
|
||||||
|
|
||||||
|
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||||
|
with_str(value, |s| Ok(s.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&ExprInst> for u64 {
|
||||||
|
type Error = Rc<dyn ExternError>;
|
||||||
|
|
||||||
|
fn try_from(value: &ExprInst) -> Result<Self, Self::Error> {
|
||||||
|
with_uint(value, Ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,8 @@ use crate::representations::interpreted::{Clause, ExprInst};
|
|||||||
use crate::representations::{Literal, Primitive};
|
use crate::representations::{Literal, Primitive};
|
||||||
use crate::utils::unwrap_or;
|
use crate::utils::unwrap_or;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// An IO command to be handled by the host application.
|
/// An IO command to be handled by the host application.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum IO {
|
pub enum IO {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use super::super::litconv::with_str;
|
use super::super::inspect::with_str;
|
||||||
use crate::define_fn;
|
use crate::define_fn;
|
||||||
use crate::foreign::ExternError;
|
use crate::foreign::ExternError;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use super::super::litconv::with_str;
|
use super::super::inspect::with_str;
|
||||||
use super::command::IO;
|
use super::command::IO;
|
||||||
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
|
use crate::foreign::{Atomic, AtomicResult, AtomicReturn};
|
||||||
use crate::interpreter::Context;
|
use crate::interpreter::Context;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
|
||||||
|
use super::bin::bin;
|
||||||
use super::bool::bool;
|
use super::bool::bool;
|
||||||
use super::conv::conv;
|
use super::conv::conv;
|
||||||
use super::io::io;
|
use super::io::io;
|
||||||
@@ -35,7 +36,7 @@ pub fn mk_stl(i: &Interner, options: StlOptions) -> ProjectTree<VName> {
|
|||||||
let const_tree = from_const_tree(
|
let const_tree = from_const_tree(
|
||||||
HashMap::from([(
|
HashMap::from([(
|
||||||
i.i("std"),
|
i.i("std"),
|
||||||
io(i, options.impure) + conv(i) + bool(i) + str(i) + num(i),
|
io(i, options.impure) + conv(i) + bool(i) + str(i) + num(i) + bin(i),
|
||||||
)]),
|
)]),
|
||||||
&[i.i("std")],
|
&[i.i("std")],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ mod assertion_error;
|
|||||||
mod bool;
|
mod bool;
|
||||||
mod conv;
|
mod conv;
|
||||||
mod io;
|
mod io;
|
||||||
pub mod litconv;
|
pub mod inspect;
|
||||||
mod mk_stl;
|
mod mk_stl;
|
||||||
mod num;
|
mod num;
|
||||||
mod runtime_error;
|
mod runtime_error;
|
||||||
mod str;
|
mod str;
|
||||||
|
pub mod codegen;
|
||||||
|
mod bin;
|
||||||
|
|
||||||
pub use arithmetic_error::ArithmeticError;
|
pub use arithmetic_error::ArithmeticError;
|
||||||
pub use assertion_error::AssertionError;
|
pub use assertion_error::AssertionError;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
|
|
||||||
use super::litconv::with_lit;
|
use super::inspect::with_lit;
|
||||||
use super::{ArithmeticError, AssertionError};
|
use super::{ArithmeticError, AssertionError};
|
||||||
use crate::foreign::ExternError;
|
use crate::foreign::ExternError;
|
||||||
use crate::representations::interpreted::{Clause, ExprInst};
|
use crate::representations::interpreted::{Clause, ExprInst};
|
||||||
|
|||||||
@@ -1 +1,10 @@
|
|||||||
export ...$a ++ ...$b =0x4p36=> (concatenate (...$a) (...$b))
|
import super::(proc::*, bool::*, io::panic)
|
||||||
|
|
||||||
|
export ...$a ++ ...$b =0x4p36=> (concat (...$a) (...$b))
|
||||||
|
|
||||||
|
export char_at := \s.\i. do{
|
||||||
|
let slc = slice s i 1;
|
||||||
|
if len slc == 1
|
||||||
|
then slc
|
||||||
|
else panic "Character index out of bounds"
|
||||||
|
}
|
||||||
@@ -1,38 +1,87 @@
|
|||||||
use super::litconv::{with_str, with_uint};
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
use super::codegen::{orchid_opt, tuple};
|
||||||
|
use super::inspect::with_str;
|
||||||
use super::RuntimeError;
|
use super::RuntimeError;
|
||||||
use crate::interner::Interner;
|
use crate::interner::Interner;
|
||||||
|
use crate::utils::iter_find;
|
||||||
use crate::{define_fn, ConstTree, Literal};
|
use crate::{define_fn, ConstTree, Literal};
|
||||||
|
|
||||||
define_fn! {expr=x in
|
define_fn! {expr=x in
|
||||||
/// Append a string to another
|
/// Append a string to another
|
||||||
pub Concatenate {
|
pub Concatenate { a: String, b: String }
|
||||||
a: String as with_str(x, |s| Ok(s.clone())),
|
=> Ok(Literal::Str(a.to_owned() + b).into())
|
||||||
b: String as with_str(x, |s| Ok(s.clone()))
|
|
||||||
} => Ok(Literal::Str(a.to_owned() + b).into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
define_fn! {expr=x in
|
define_fn! {expr=x in
|
||||||
pub CharAt {
|
pub Slice { s: String, i: u64, len: u64 } => {
|
||||||
s: String as with_str(x, |s| Ok(s.clone())),
|
let graphs = s.graphemes(true);
|
||||||
i: u64 as with_uint(x, Ok)
|
if *i == 0 {
|
||||||
} => {
|
Ok(Literal::Str(graphs.take(*len as usize).collect()).into())
|
||||||
if let Some(c) = s.chars().nth(*i as usize) {
|
|
||||||
Ok(Literal::Char(c).into())
|
|
||||||
} else {
|
} else {
|
||||||
RuntimeError::fail(
|
let mut prefix = graphs.skip(*i as usize - 1);
|
||||||
"Character index out of bounds".to_string(),
|
if prefix.next().is_none() {
|
||||||
"indexing string",
|
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 {
|
pub fn str(i: &Interner) -> ConstTree {
|
||||||
ConstTree::tree([(
|
ConstTree::tree([(
|
||||||
i.i("str"),
|
i.i("str"),
|
||||||
ConstTree::tree([
|
ConstTree::tree([
|
||||||
(i.i("concatenate"), ConstTree::xfn(Concatenate)),
|
(i.i("concat"), ConstTree::xfn(Concatenate)),
|
||||||
(i.i("char_at"), ConstTree::xfn(CharAt)),
|
(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)),
|
||||||
]),
|
]),
|
||||||
)])
|
)])
|
||||||
}
|
}
|
||||||
|
|||||||
47
src/utils/iter_find.rs
Normal file
47
src/utils/iter_find.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/// Check if the finite sequence produced by a clonable iterator (`haystack`)
|
||||||
|
/// contains the finite sequence produced by another clonable iterator
|
||||||
|
/// (`needle`)
|
||||||
|
pub fn iter_find<T: Eq>(
|
||||||
|
mut haystack: impl Iterator<Item = T> + Clone,
|
||||||
|
needle: impl Iterator<Item = T> + Clone,
|
||||||
|
) -> Option<usize> {
|
||||||
|
let mut start = 0;
|
||||||
|
loop {
|
||||||
|
match iter_starts_with(haystack.clone(), needle.clone()) {
|
||||||
|
ISWResult::StartsWith => return Some(start),
|
||||||
|
ISWResult::Shorter => return None,
|
||||||
|
ISWResult::Difference => (),
|
||||||
|
}
|
||||||
|
haystack.next();
|
||||||
|
start += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Value returned by iter_starts_with
|
||||||
|
enum ISWResult {
|
||||||
|
/// The first iterator starts with the second
|
||||||
|
StartsWith,
|
||||||
|
/// The values of the two iterators differ
|
||||||
|
Difference,
|
||||||
|
/// The first iterator ends before the second
|
||||||
|
Shorter,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks that an iterator starts with another
|
||||||
|
fn iter_starts_with<T: Eq>(
|
||||||
|
mut a: impl Iterator<Item = T>,
|
||||||
|
b: impl Iterator<Item = T>,
|
||||||
|
) -> ISWResult {
|
||||||
|
// if a starts with b then for every element in b
|
||||||
|
for item in b {
|
||||||
|
// a has to contain the same element
|
||||||
|
if let Some(comp) = a.next() {
|
||||||
|
if item != comp {
|
||||||
|
return ISWResult::Difference;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ISWResult::Shorter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ISWResult::StartsWith
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ mod split_max_prefix;
|
|||||||
mod string_from_charset;
|
mod string_from_charset;
|
||||||
mod substack;
|
mod substack;
|
||||||
mod unwrap_or;
|
mod unwrap_or;
|
||||||
|
mod iter_find;
|
||||||
|
|
||||||
pub use cache::Cache;
|
pub use cache::Cache;
|
||||||
pub use print_nname::sym2string;
|
pub use print_nname::sym2string;
|
||||||
@@ -21,3 +22,4 @@ pub(crate) use unwrap_or::unwrap_or;
|
|||||||
pub mod iter;
|
pub mod iter;
|
||||||
pub use iter::BoxedIter;
|
pub use iter::BoxedIter;
|
||||||
pub use string_from_charset::string_from_charset;
|
pub use string_from_charset::string_from_charset;
|
||||||
|
pub use iter_find::iter_find;
|
||||||
|
|||||||
Reference in New Issue
Block a user