Working example

This commit is contained in:
2023-03-10 13:58:16 +00:00
parent 35a081162f
commit 180ebb56fa
62 changed files with 1487 additions and 372 deletions

View File

@@ -1,15 +1,21 @@
TRUE := \t.\f.t import std::conv::(parse_float, to_string)
FALSE := \t.\f.f import std::cpsio::(readline, print)
NOT := \x.x FALSE TRUE import std::str::(concatenate)
AND := \x.\y.x y FALSE
OR := \x.\y. x TRUE y
Y := \f.(\x.f (x x))(\x.f (x x))
(! ...$expr) =10=> (NOT ...$expr) export main := do{
(...$lhs & ...$rhs) =10=> (AND (...$lhs) (...$rhs)) cps data = readline;
(...$lhs | ...$rhs) =20=> (OR (...$lhs) (...$rhs)) let a = parse_float data;
cps op = readline;
main := (TRUE & TRUE | FALSE & FALSE) cps print ("\"" ++ op ++ "\"\n");
cps data = readline;
(start_token ...$rest) ==> (carriage(()) ...$rest) let b = parse_float data;
(..$prefix carriage($state) $next ..$rest) ==> (..$prefix $out carriage(??) ..$rest) let result = (
if op = "+" then a + b
else if op = "-" then a - b
else if op = "*" then a * b
else if op = "/" then a / b
else "Unsupported operation" -- dynamically typed shenanigans
);
cps print (to_string result ++ "\n");
0
}

View File

@@ -0,0 +1,66 @@
# Introduction
Orchid is a lazy, pure functional programming language with an execution model inspired by Haskell and a powerful syntax-level preprocessor for encoding rich DSLs that adhere to the language's core guarantees.
# Immutability
The merits of pure functional code are well known, but I would like to highlight some of them that are particularly relevant in the case of Orchid;
- **Free execution order** The value of any particular subprogram is largely independent of its execution order, so externally defined functions have a lot of liberty in evaluating their arguments. This can ensure that errors are caught early, or even be used to start subtasks in parallel while the rest of the parameters are being collected. With a considerately designed external API, Orchid functions can be reordered and parallelized based on the resources they operate on. This approach can be observed in Rust's Bevy ECS, but Rust is an impure language so it can only guarantee this degree of safety at the cost of great complexity.
- **Self-containment** Arguments to the current toplevel function are all completely self-contained expressions, which means that they can be serialized and sent over the network provided that an equivalent for all atoms and externally defined functions exists on both sides, which makes Orchid a prime query language.
> **note**
> Although this is possible using Javascript's `Function` constructor, it is a catastrophic security vulnerability since code sent this way can access all host APIs. In the case of Orchid it is not only perfectly safe from an information access perspective since all references are bound on the sender side and undergo explicit translation, but also from a computational resource perspective since external functions allow the recipient to apply step limits (gas) to the untrusted expression, interleave it with local tasks, and monitor its size and memory footprint.
- **reentrancy** in low reliability environments it is common to run multiple instances of an algorithm in parallel and regularly compare and correct their state using some form of consensus. In an impure language this must be done explicitly and mistakes can result in divergence. In a pure language the executor can be configured to check its state with others every so many steps.
# Laziness
Reactive programming is an increasingly popular paradigm for enabling systems to interact with changing state without recomputing subresults that have not been modified. It is getting popular despite the fact that enabling this kind of programming in classical languages - most notably javascript, where it appears to be the most popular - involves lots of boilerplate and complicated constructs using many many lambda functions. In a lazy language this is essentially the default.
In addition, lazy, pure code lends itself to optimization. Deforestation and TCO are implied and CTFE (or in the case of an interpreted language ahead-of-time function execution) along with a host of other optimizations are more convenient.
# Macros
One major grievance of mine with Haskell is that its syntax isn't accessible. Even after understanding the rules, getting used to reading it takes considerable time. On the other hand, I really like the way Rust enables library developers to invent their own syntax that intuitively describes the concepts the library at hand encodes. In Orchid's codebase, I defined several macros to streamline tasks like defining functions in Rust that are visible to Orchid.
## Generalized kerning
In the referenced video essay, a proof of the Turing completeness of generalized kerning is presented. The proof involves encoding a Turing machine in a string and some kerning rules. The state of the machine is next to the read-write head and all previous states are enumerated next to the tape because kerning rules are reversible. The end result looks something like this:
```
abcbcddddef|1110000110[0]a00111010011101110
```
The rules are translated into kerning rules. For a rule
> in state `a` seeing `0`: new state is `b`, write `1` and go `left`
the kerning rule would look like this (template instantiated for all possible characters):
```
$1 [ 0 ] a equals a < $1 ] b 0
```
Some global rules are also needed, also instantiated for all possible characters in the templated positions
```
$1 $2 < equals $2 < $1 unless $1 is |
| $1 < equals $1 | >
> $1 $2 equals $1 > $2 unless $2 is ]
> $1 ] equals [ $1 ]
```
What I really appreciate in this proof is how visual it is; based on this, it's easy to imagine how one would go about encoding a pushdown automaton, lambda calculus or other interesting tree-walking procedures. This is exactly why I based my preprocessor on this system.
## Namespaced tokens
I found two major problems with C and Rust macros which vastly limit their potential. They're relatively closed systems, and prone to aliasing. Every other item in Rust follows a rigorous namespacing scheme, but the macros break this seal, I presume the reason is that macro execution happens before namespace resolution.
Orchid's macros - substitution rules - operate on namespaced tokens. This means that users can safely give their macros short and intuitive names, but it also means that the macros can hook into each other. Consider for example the following hypothetical example.
a widely known module implements a unique way of transforming iterators using an SQL-like syntax.
```orchid
select ...$collist from ...$
```

View File

@@ -4,7 +4,20 @@
"path": "." "path": "."
} }
], ],
"settings": {}, "settings": {
"[markdown]": {
"editor.unicodeHighlight.ambiguousCharacters": false,
"editor.unicodeHighlight.invisibleCharacters": false,
"diffEditor.ignoreTrimWhitespace": false,
"editor.wordWrap": "bounded",
"editor.wordWrapColumn": 100,
"editor.quickSuggestions": {
"comments": "off",
"strings": "off",
"other": "off"
}
}
},
"extensions": { "extensions": {
"recommendations": [ "recommendations": [
"tomoki1207.pdf", "tomoki1207.pdf",

View File

@@ -16,8 +16,8 @@ impl AssertionError {
return Err(Self { value, assertion }.into_extern()) return Err(Self { value, assertion }.into_extern())
} }
pub fn into_extern(self) -> Rc<dyn ExternError> { pub fn ext(value: Clause, assertion: &'static str) -> Rc<dyn ExternError> {
Rc::new(self) return Self { value, assertion }.into_extern()
} }
} }

21
src/external/bool/boolean.rs vendored Normal file
View File

@@ -0,0 +1,21 @@
use crate::{atomic_inert, representations::{interpreted::Clause, Primitive}, foreign::Atom};
#[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<'a> TryFrom<&'a Clause> for Boolean {
type Error = ();
fn try_from(value: &'a Clause) -> Result<Self, Self::Error> {
if let Clause::P(Primitive::Atom(Atom(a))) = value {
if let Some(b) = a.as_any().downcast_ref::<Boolean>() {
return Ok(*b)
}
}
return Err(())
}
}

52
src/external/bool/equals.rs vendored Normal file
View File

@@ -0,0 +1,52 @@
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::foreign::Atom;
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
use super::super::assertion_error::AssertionError;
use super::boolean::Boolean;
/// Equals function
///
/// Next state: [Equals1]
#[derive(Clone)]
pub struct Equals2;
externfn_impl!(Equals2, |_: &Self, c: Clause| {Ok(Equals1{c})});
/// Partially applied Equals function
///
/// Prev state: [Equals2]; Next state: [Equals0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Equals1{ c: Clause }
atomic_redirect!(Equals1, c);
atomic_impl!(Equals1);
externfn_impl!(Equals1, |this: &Self, c: Clause| {
let a: Literal = this.c.clone().try_into()
.map_err(|_| AssertionError::ext(this.c.clone(), "a primitive"))?;
Ok(Equals0{ a, c })
});
/// Fully applied Equals function.
///
/// Prev state: [Equals1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Equals0 { a: Literal, c: Clause }
atomic_redirect!(Equals0, c);
atomic_impl!(Equals0, |Self{ a, c }: &Self| {
let b: Literal = c.clone().try_into()
.map_err(|_| AssertionError::ext(c.clone(), "a literal value"))?;
let eqls = 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::Uint(i1), Literal::Uint(i2)) => *i1 == i2,
(_, _) => AssertionError::fail(c.clone(), "the expected type")?,
};
Ok(Clause::P(Primitive::Atom(Atom::new(Boolean::from(eqls)))))
});

43
src/external/bool/ifthenelse.rs vendored Normal file
View File

@@ -0,0 +1,43 @@
use std::fmt::Debug;
use std::hash::Hash;
use std::rc::Rc;
use crate::external::assertion_error::AssertionError;
use crate::representations::PathSet;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
use super::Boolean;
/// IfThenElse function
///
/// Next state: [IfThenElse0]
#[derive(Clone)]
pub struct IfThenElse1;
externfn_impl!(IfThenElse1, |_: &Self, c: Clause| {Ok(IfThenElse0{c})});
/// Partially applied IfThenElse function
///
/// Prev state: [IfThenElse1]; Next state: [IfThenElse0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct IfThenElse0{ c: Clause }
atomic_redirect!(IfThenElse0, c);
atomic_impl!(IfThenElse0, |this: &Self| {
let Boolean(b) = (&this.c).try_into()
.map_err(|_| AssertionError::ext(this.c.clone(), "a boolean"))?;
Ok(if b { Clause::Lambda {
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
body: Rc::new(Clause::Lambda {
args: None,
body: Rc::new(Clause::LambdaArg)
})
}} else { Clause::Lambda {
args: None,
body: Rc::new(Clause::Lambda {
args: Some(PathSet { steps: Rc::new(vec![]), next: None }),
body: Rc::new(Clause::LambdaArg)
})
}})
});

13
src/external/bool/mod.rs vendored Normal file
View File

@@ -0,0 +1,13 @@
mod equals;
mod boolean;
mod ifthenelse;
pub use boolean::Boolean;
use crate::project::{Loader, fnlib_loader};
pub fn bool() -> impl Loader {
fnlib_loader(vec![
("ifthenelse", Box::new(ifthenelse::IfThenElse1)),
("equals", Box::new(equals::Equals2))
])
}

13
src/external/conv/mod.rs vendored Normal file
View File

@@ -0,0 +1,13 @@
use crate::project::{fnlib_loader, Loader};
mod to_string;
mod parse_float;
mod parse_uint;
pub fn conv() -> impl Loader {
fnlib_loader(vec![
("parse_float", Box::new(parse_float::ParseFloat1)),
("parse_uint", Box::new(parse_uint::ParseUint1)),
("to_string", Box::new(to_string::ToString1))
])
}

46
src/external/conv/parse_float.rs vendored Normal file
View File

@@ -0,0 +1,46 @@
use chumsky::Parser;
use std::fmt::Debug;
use std::hash::Hash;
use super::super::assertion_error::AssertionError;
use crate::parse::float_parser;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::foreign::ExternError;
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
/// ParseFloat a number
///
/// Next state: [ParseFloat0]
#[derive(Clone)]
pub struct ParseFloat1;
externfn_impl!(ParseFloat1, |_: &Self, c: Clause| {Ok(ParseFloat0{c})});
/// Applied to_string function
///
/// Prev state: [ParseFloat1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct ParseFloat0{ c: Clause }
atomic_redirect!(ParseFloat0, c);
atomic_impl!(ParseFloat0, |Self{ c }: &Self| {
let literal: &Literal = c.try_into()
.map_err(|_| AssertionError::ext(c.clone(), "a literal value"))?;
let number = match literal {
Literal::Str(s) => {
let parser = float_parser();
parser.parse(s.as_str()).map_err(|_| AssertionError{
value: c.clone(), assertion: "cannot be parsed into a float"
}.into_extern())?
}
Literal::Num(n) => *n,
Literal::Uint(i) => (*i as u32).into(),
Literal::Char(char) => char.to_digit(10).ok_or(AssertionError{
value: c.clone(), assertion: "is not a decimal digit"
}.into_extern())?.into()
};
Ok(Clause::P(Primitive::Literal(Literal::Num(number))))
});

46
src/external/conv/parse_uint.rs vendored Normal file
View File

@@ -0,0 +1,46 @@
use chumsky::Parser;
use std::fmt::Debug;
use std::hash::Hash;
use super::super::assertion_error::AssertionError;
use crate::parse::int_parser;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::foreign::ExternError;
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
/// Parse a number
///
/// Next state: [ParseUint0]
#[derive(Clone)]
pub struct ParseUint1;
externfn_impl!(ParseUint1, |_: &Self, c: Clause| {Ok(ParseUint0{c})});
/// Applied ParseUint function
///
/// Prev state: [ParseUint1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct ParseUint0{ c: Clause }
atomic_redirect!(ParseUint0, c);
atomic_impl!(ParseUint0, |Self{ c }: &Self| {
let literal: &Literal = c.try_into()
.map_err(|_| AssertionError::ext(c.clone(), "a literal value"))?;
let uint = match literal {
Literal::Str(s) => {
let parser = int_parser();
parser.parse(s.as_str()).map_err(|_| AssertionError{
value: c.clone(), assertion: "cannot be parsed into an unsigned int"
}.into_extern())?
}
Literal::Num(n) => n.floor() as u64,
Literal::Uint(i) => *i,
Literal::Char(char) => char.to_digit(10).ok_or(AssertionError{
value: c.clone(), assertion: "is not a decimal digit"
}.into_extern())? as u64
};
Ok(Clause::P(Primitive::Literal(Literal::Uint(uint))))
});

35
src/external/conv/to_string.rs vendored Normal file
View File

@@ -0,0 +1,35 @@
use std::fmt::Debug;
use std::hash::Hash;
use crate::external::assertion_error::AssertionError;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
/// ToString a clause
///
/// Next state: [ToString0]
#[derive(Clone)]
pub struct ToString1;
externfn_impl!(ToString1, |_: &Self, c: Clause| {Ok(ToString0{c})});
/// Applied ToString function
///
/// Prev state: [ToString1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct ToString0{ c: Clause }
atomic_redirect!(ToString0, c);
atomic_impl!(ToString0, |Self{ c }: &Self| {
let literal: &Literal = c.try_into()
.map_err(|_| AssertionError::ext(c.clone(), "a literal value"))?;
let string = match literal {
Literal::Char(c) => c.to_string(),
Literal::Uint(i) => i.to_string(),
Literal::Num(n) => n.to_string(),
Literal::Str(s) => s.clone()
};
Ok(Clause::P(Primitive::Literal(Literal::Str(string))))
});

11
src/external/cpsio/mod.rs vendored Normal file
View File

@@ -0,0 +1,11 @@
use crate::project::{Loader, fnlib_loader};
mod print;
mod readline;
pub fn cpsio() -> impl Loader {
fnlib_loader(vec![
("print", Box::new(print::Print2)),
("readline", Box::new(readline::Readln2))
])
}

32
src/external/cpsio/print.rs vendored Normal file
View File

@@ -0,0 +1,32 @@
use std::fmt::Debug;
use std::hash::Hash;
use std::rc::Rc;
use crate::external::str::cls2str;
use crate::representations::PathSet;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
/// Print function
///
/// Next state: [Print1]
#[derive(Clone)]
pub struct Print2;
externfn_impl!(Print2, |_: &Self, c: Clause| {Ok(Print1{c})});
/// Partially applied Print function
///
/// Prev state: [Print2]; Next state: [Print0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Print1{ c: Clause }
atomic_redirect!(Print1, c);
atomic_impl!(Print1, |Self{ c }: &Self| {
let message = cls2str(&c)?;
print!("{}", message);
Ok(Clause::Lambda {
args: Some(PathSet{ steps: Rc::new(vec![]), next: None }),
body: Rc::new(Clause::LambdaArg)
})
});

35
src/external/cpsio/readline.rs vendored Normal file
View File

@@ -0,0 +1,35 @@
use std::fmt::Debug;
use std::io::stdin;
use std::rc::Rc;
use std::hash::Hash;
use crate::external::runtime_error::RuntimeError;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
/// Readln function
///
/// Next state: [Readln1]
#[derive(Clone)]
pub struct Readln2;
externfn_impl!(Readln2, |_: &Self, c: Clause| {Ok(Readln1{c})});
/// Partially applied Readln function
///
/// Prev state: [Readln2]; Next state: [Readln0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Readln1{ c: Clause }
atomic_redirect!(Readln1, c);
atomic_impl!(Readln1, |Self{ c }: &Self| {
let mut buf = String::new();
stdin().read_line(&mut buf).map_err(|e| RuntimeError::ext(e.to_string(), "reading from stdin"))?;
buf.pop();
Ok(Clause::Apply {
f: Rc::new(c.clone()),
x: Rc::new(Clause::P(Primitive::Literal(Literal::Str(buf)))),
id: 0
})
});

10
src/external/mod.rs vendored
View File

@@ -1,4 +1,8 @@
mod numbers; mod num;
mod assertion_error; mod assertion_error;
pub mod std;
use numbers::Multiply2; mod conv;
mod str;
mod cpsio;
mod runtime_error;
mod bool;

15
src/external/num/mod.rs vendored Normal file
View File

@@ -0,0 +1,15 @@
mod numeric;
pub mod operators;
pub use numeric::Numeric;
use crate::project::{fnlib_loader, Loader};
pub fn num() -> impl Loader {
fnlib_loader(vec![
("add", Box::new(operators::add::Add2)),
("subtract", Box::new(operators::subtract::Subtract2)),
("multiply", Box::new(operators::multiply::Multiply2)),
("divide", Box::new(operators::divide::Divide2)),
("remainder", Box::new(operators::remainder::Remainder2))
])
}

View File

@@ -10,7 +10,7 @@ use crate::representations::interpreted::Clause;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Numeric { pub enum Numeric {
Int(u64), Uint(u64),
Num(NotNan<f64>) Num(NotNan<f64>)
} }
@@ -19,9 +19,9 @@ impl Add for Numeric {
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
match (self, rhs) { match (self, rhs) {
(Numeric::Int(a), Numeric::Int(b)) => Numeric::Int(a + b), (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a + b),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a + b), (Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a + b),
(Numeric::Int(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Int(a)) (Numeric::Uint(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Uint(a))
=> Numeric::Num(NotNan::new(a as f64).unwrap() + b) => Numeric::Num(NotNan::new(a as f64).unwrap() + b)
} }
} }
@@ -32,11 +32,11 @@ impl Sub for Numeric {
fn sub(self, rhs: Self) -> Self::Output { fn sub(self, rhs: Self) -> Self::Output {
match (self, rhs) { match (self, rhs) {
(Numeric::Int(a), Numeric::Int(b)) if b < a => Numeric::Int(a - b), (Numeric::Uint(a), Numeric::Uint(b)) if b < a => Numeric::Uint(a - b),
(Numeric::Int(a), Numeric::Int(b)) (Numeric::Uint(a), Numeric::Uint(b))
=> Numeric::Num(NotNan::new(a as f64 - b as f64).unwrap()), => Numeric::Num(NotNan::new(a as f64 - b as f64).unwrap()),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a - b), (Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a - b),
(Numeric::Int(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Int(a)) (Numeric::Uint(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Uint(a))
=> Numeric::Num(NotNan::new(a as f64).unwrap() - b) => Numeric::Num(NotNan::new(a as f64).unwrap() - b)
} }
} }
@@ -47,9 +47,9 @@ impl Mul for Numeric {
fn mul(self, rhs: Self) -> Self::Output { fn mul(self, rhs: Self) -> Self::Output {
match (self, rhs) { match (self, rhs) {
(Numeric::Int(a), Numeric::Int(b)) => Numeric::Int(a * b), (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a * b),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a * b), (Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a * b),
(Numeric::Int(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Int(a)) (Numeric::Uint(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Uint(a))
=> Numeric::Num(NotNan::new(a as f64).unwrap() * b) => Numeric::Num(NotNan::new(a as f64).unwrap() * b)
} }
} }
@@ -59,8 +59,8 @@ impl Div for Numeric {
type Output = Numeric; type Output = Numeric;
fn div(self, rhs: Self) -> Self::Output { fn div(self, rhs: Self) -> Self::Output {
let a = match self { Numeric::Int(i) => i as f64, Numeric::Num(f) => *f }; let a = match self { Numeric::Uint(i) => i as f64, Numeric::Num(f) => *f };
let b = match rhs { Numeric::Int(i) => i as f64, Numeric::Num(f) => *f }; let b = match rhs { Numeric::Uint(i) => i as f64, Numeric::Num(f) => *f };
Numeric::Num(NotNan::new(a / b).unwrap()) Numeric::Num(NotNan::new(a / b).unwrap())
} }
} }
@@ -70,9 +70,9 @@ impl Rem for Numeric {
fn rem(self, rhs: Self) -> Self::Output { fn rem(self, rhs: Self) -> Self::Output {
match (self, rhs) { match (self, rhs) {
(Numeric::Int(a), Numeric::Int(b)) => Numeric::Int(a % b), (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a % b),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a % b), (Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(a % b),
(Numeric::Int(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Int(a)) (Numeric::Uint(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Uint(a))
=> Numeric::Num(NotNan::new(a as f64).unwrap() % b) => Numeric::Num(NotNan::new(a as f64).unwrap() % b)
} }
} }
@@ -85,7 +85,7 @@ impl TryFrom<Clause> for Numeric {
AssertionError::fail(value, "a literal value")? AssertionError::fail(value, "a literal value")?
}; };
match l { match l {
Literal::Int(i) => Ok(Numeric::Int(i)), Literal::Uint(i) => Ok(Numeric::Uint(i)),
Literal::Num(n) => Ok(Numeric::Num(n)), Literal::Num(n) => Ok(Numeric::Num(n)),
_ => AssertionError::fail(value, "an integer or number")? _ => AssertionError::fail(value, "an integer or number")?
} }
@@ -95,8 +95,17 @@ impl TryFrom<Clause> for Numeric {
impl From<Numeric> for Clause { impl From<Numeric> for Clause {
fn from(value: Numeric) -> Self { fn from(value: Numeric) -> Self {
Clause::P(Primitive::Literal(match value { Clause::P(Primitive::Literal(match value {
Numeric::Int(i) => Literal::Int(i), Numeric::Uint(i) => Literal::Uint(i),
Numeric::Num(n) => Literal::Num(n) 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()
}
}
}

41
src/external/num/operators/add.rs vendored Normal file
View File

@@ -0,0 +1,41 @@
use super::super::Numeric;
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
/// Add function
///
/// Next state: [Add1]
#[derive(Clone)]
pub struct Add2;
externfn_impl!(Add2, |_: &Self, c: Clause| {Ok(Add1{c})});
/// Partially applied Add function
///
/// Prev state: [Add2]; Next state: [Add0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Add1{ c: Clause }
atomic_redirect!(Add1, c);
atomic_impl!(Add1);
externfn_impl!(Add1, |this: &Self, c: Clause| {
let a: Numeric = this.c.clone().try_into()?;
Ok(Add0{ a, c })
});
/// Fully applied Add function.
///
/// Prev state: [Add1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Add0 { a: Numeric, c: Clause }
atomic_redirect!(Add0, c);
atomic_impl!(Add0, |Self{ a, c }: &Self| {
let b: Numeric = c.clone().try_into()?;
Ok((*a + b).into())
});

41
src/external/num/operators/divide.rs vendored Normal file
View File

@@ -0,0 +1,41 @@
use super::super::Numeric;
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
/// Divide function
///
/// Next state: [Divide1]
#[derive(Clone)]
pub struct Divide2;
externfn_impl!(Divide2, |_: &Self, c: Clause| {Ok(Divide1{c})});
/// Partially applied Divide function
///
/// Prev state: [Divide2]; Next state: [Divide0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Divide1{ c: Clause }
atomic_redirect!(Divide1, c);
atomic_impl!(Divide1);
externfn_impl!(Divide1, |this: &Self, c: Clause| {
let a: Numeric = this.c.clone().try_into()?;
Ok(Divide0{ a, c })
});
/// Fully applied Divide function.
///
/// Prev state: [Divide1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Divide0 { a: Numeric, c: Clause }
atomic_redirect!(Divide0, c);
atomic_impl!(Divide0, |Self{ a, c }: &Self| {
let b: Numeric = c.clone().try_into()?;
Ok((*a / b).into())
});

5
src/external/num/operators/mod.rs vendored Normal file
View File

@@ -0,0 +1,5 @@
pub mod add;
pub mod divide;
pub mod multiply;
pub mod remainder;
pub mod subtract;

View File

@@ -1,14 +1,11 @@
mod numeric;
use numeric::Numeric; use super::super::Numeric;
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc;
use std::hash::Hash; use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl}; use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::foreign::{ExternError, ExternFn, Atom, Atomic}; use crate::representations::interpreted::Clause;
use crate::representations::Primitive;
use crate::representations::interpreted::{Clause, InternalError};
/// Multiply function /// Multiply function
/// ///

41
src/external/num/operators/remainder.rs vendored Normal file
View File

@@ -0,0 +1,41 @@
use super::super::Numeric;
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
/// Remainder function
///
/// Next state: [Remainder1]
#[derive(Clone)]
pub struct Remainder2;
externfn_impl!(Remainder2, |_: &Self, c: Clause| {Ok(Remainder1{c})});
/// Partially applied Remainder function
///
/// Prev state: [Remainder2]; Next state: [Remainder0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Remainder1{ c: Clause }
atomic_redirect!(Remainder1, c);
atomic_impl!(Remainder1);
externfn_impl!(Remainder1, |this: &Self, c: Clause| {
let a: Numeric = this.c.clone().try_into()?;
Ok(Remainder0{ a, c })
});
/// Fully applied Remainder function.
///
/// Prev state: [Remainder1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Remainder0 { a: Numeric, c: Clause }
atomic_redirect!(Remainder0, c);
atomic_impl!(Remainder0, |Self{ a, c }: &Self| {
let b: Numeric = c.clone().try_into()?;
Ok((*a % b).into())
});

41
src/external/num/operators/subtract.rs vendored Normal file
View File

@@ -0,0 +1,41 @@
use super::super::Numeric;
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::{Clause};
/// Subtract function
///
/// Next state: [Subtract1]
#[derive(Clone)]
pub struct Subtract2;
externfn_impl!(Subtract2, |_: &Self, c: Clause| {Ok(Subtract1{c})});
/// Partially applied Subtract function
///
/// Prev state: [Subtract2]; Next state: [Subtract0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Subtract1{ c: Clause }
atomic_redirect!(Subtract1, c);
atomic_impl!(Subtract1);
externfn_impl!(Subtract1, |this: &Self, c: Clause| {
let a: Numeric = this.c.clone().try_into()?;
Ok(Subtract0{ a, c })
});
/// Fully applied Subtract function.
///
/// Prev state: [Subtract1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Subtract0 { a: Numeric, c: Clause }
atomic_redirect!(Subtract0, c);
atomic_impl!(Subtract0, |Self{ a, c }: &Self| {
let b: Numeric = c.clone().try_into()?;
Ok((*a - b).into())
});

27
src/external/runtime_error.rs vendored Normal file
View File

@@ -0,0 +1,27 @@
use std::{rc::Rc, fmt::Display};
use crate::foreign::ExternError;
#[derive(Clone)]
pub struct RuntimeError {
message: String,
operation: &'static str,
}
impl RuntimeError {
pub fn fail(message: String, operation: &'static str) -> Result<!, 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{}

19
src/external/std.rs vendored Normal file
View File

@@ -0,0 +1,19 @@
use std::collections::HashMap;
use crate::project::{map_loader, Loader};
use super::bool::bool;
use super::cpsio::cpsio;
use super::conv::conv;
use super::str::str;
use super::num::num;
pub fn std() -> impl Loader {
map_loader(HashMap::from([
("cpsio", cpsio().boxed()),
("conv", conv().boxed()),
("bool", bool().boxed()),
("str", str().boxed()),
("num", num().boxed()),
]))
}

47
src/external/str/char_at.rs vendored Normal file
View File

@@ -0,0 +1,47 @@
use std::fmt::Debug;
use std::hash::Hash;
use crate::external::assertion_error::AssertionError;
use crate::external::runtime_error::RuntimeError;
use crate::representations::{Literal, Primitive};
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::interpreted::Clause;
/// CharAt function
///
/// Next state: [CharAt1]
#[derive(Clone)]
pub struct CharAt2;
externfn_impl!(CharAt2, |_: &Self, c: Clause| {Ok(CharAt1{c})});
/// Partially applied CharAt function
///
/// Prev state: [CharAt2]; Next state: [CharAt0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct CharAt1{ c: Clause }
atomic_redirect!(CharAt1, c);
atomic_impl!(CharAt1);
externfn_impl!(CharAt1, |this: &Self, c: Clause| {
let s = if let Ok(Literal::Str(s)) = this.c.clone().try_into() {s}
else {AssertionError::fail(this.c.clone(), "a string")?};
Ok(CharAt0{ s, c })
});
/// Fully applied CharAt function.
///
/// Prev state: [CharAt1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct CharAt0 { s: String, c: Clause }
atomic_redirect!(CharAt0, c);
atomic_impl!(CharAt0, |Self{ s, c }: &Self| {
let i = if let Ok(Literal::Uint(i)) = c.clone().try_into() {i}
else {AssertionError::fail(c.clone(), "an uint")?};
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")?
}
});

13
src/external/str/cls2str.rs vendored Normal file
View File

@@ -0,0 +1,13 @@
use std::rc::Rc;
use crate::foreign::ExternError;
use crate::external::assertion_error::AssertionError;
use crate::representations::{interpreted::Clause, Literal};
pub fn cls2str(c: &Clause) -> Result<&String, Rc<dyn ExternError>> {
let literal: &Literal = c.try_into()
.map_err(|_| AssertionError::ext(c.clone(), "a literal value"))?;
if let Literal::Str(s) = literal {Ok(s)} else {
AssertionError::fail(c.clone(), "a string")?
}
}

41
src/external/str/concatenate.rs vendored Normal file
View File

@@ -0,0 +1,41 @@
use super::cls2str;
use std::fmt::Debug;
use std::hash::Hash;
use crate::{atomic_impl, atomic_redirect, externfn_impl};
use crate::representations::{Primitive, Literal};
use crate::representations::interpreted::Clause;
/// Concatenate function
///
/// Next state: [Concatenate1]
#[derive(Clone)]
pub struct Concatenate2;
externfn_impl!(Concatenate2, |_: &Self, c: Clause| {Ok(Concatenate1{c})});
/// Partially applied Concatenate function
///
/// Prev state: [Concatenate2]; Next state: [Concatenate0]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Concatenate1{ c: Clause }
atomic_redirect!(Concatenate1, c);
atomic_impl!(Concatenate1);
externfn_impl!(Concatenate1, |this: &Self, c: Clause| {
let a: String = cls2str(&this.c)?.clone();
Ok(Concatenate0{ a, c })
});
/// Fully applied Concatenate function.
///
/// Prev state: [Concatenate1]
#[derive(Debug, Clone, PartialEq, Hash)]
pub struct Concatenate0 { a: String, c: Clause }
atomic_redirect!(Concatenate0, c);
atomic_impl!(Concatenate0, |Self{ a, c }: &Self| {
let b: &String = cls2str(c)?;
Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + b))))
});

11
src/external/str/mod.rs vendored Normal file
View File

@@ -0,0 +1,11 @@
mod concatenate;
mod cls2str;
mod char_at;
pub use cls2str::cls2str;
use crate::project::{Loader, fnlib_loader};
pub fn str() -> impl Loader {
fnlib_loader(vec![
("concatenate", Box::new(concatenate::Concatenate2))
])
}

View File

@@ -7,15 +7,17 @@ use dyn_clone::DynClone;
use crate::representations::interpreted::{Clause, RuntimeError, InternalError}; use crate::representations::interpreted::{Clause, RuntimeError, InternalError};
pub trait ExternError: Display {} pub trait ExternError: Display {
fn into_extern(self) -> Rc<dyn ExternError> where Self: 'static + Sized {
Rc::new(self)
}
}
/// Represents an externally defined function from the perspective of the executor /// Represents an externally defined function from the perspective of the executor
/// Since Orchid lacks basic numerical operations, these are also external functions. /// Since Orchid lacks basic numerical operations, these are also external functions.
pub trait ExternFn: DynClone { pub trait ExternFn: DynClone {
fn name(&self) -> &str; fn name(&self) -> &str;
fn apply(&self, arg: Clause) -> Result<Clause, Rc<dyn ExternError>>; fn apply(&self, arg: Clause) -> Result<Clause, Rc<dyn ExternError>>;
fn argstr(&self) -> &str { "clause" }
fn retstr(&self) -> &str { "clause" }
fn hash(&self, state: &mut dyn std::hash::Hasher) { state.write_str(self.name()) } fn hash(&self, state: &mut dyn std::hash::Hasher) { state.write_str(self.name()) }
} }
@@ -28,7 +30,7 @@ impl Hash for dyn ExternFn {
} }
impl Debug for dyn ExternFn { impl Debug for dyn ExternFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "##EXTERN[{}]:{:?} -> {:?}##", self.name(), self.argstr(), self.retstr()) write!(f, "##EXTERN[{}]##", self.name())
} }
} }

View File

@@ -1,147 +0,0 @@
#[allow(unused)] // for the doc comments
use crate::representations::Primitive;
#[allow(unused)] // for the doc comments
use crate::foreign::{Atomic, ExternFn};
#[allow(unused)] // for the doc comments
use std::any::Any;
#[allow(unused)] // for the doc comments
use std::hash::Hash;
#[allow(unused)] // for the doc comments
use dyn_clone::DynClone;
#[allow(unused)] // for the doc comments
use std::fmt::Debug;
/// A macro that generates the straightforward, syntactically invariant part of implementing
/// [Atomic]. Implemented fns are [Atomic::as_any], [Atomic::definitely_eq] and [Atomic::hash].
///
/// It depends on [Eq] and [Hash]
#[macro_export]
macro_rules! atomic_defaults {
() => {
fn as_any(&self) -> &dyn std::any::Any { self }
fn definitely_eq(&self, _other: &dyn std::any::Any) -> bool {
_other.downcast_ref().map(|o| self == o).unwrap_or(false)
}
fn hash(&self, mut hasher: &mut dyn std::hash::Hasher) { <Self as Hash>::hash(self, &mut hasher) }
};
}
/// A macro that generates implementations of [Atomic] to simplify the development of
/// external bindings for Orchid.
///
/// The macro depends on implementations of [AsRef<Clause>] and [From<(&Self, Clause)>] for
/// extracting the clause to be processed and then reconstructing the [Atomic]. Naturally,
/// supertraits of [Atomic] are also dependencies. These are [Any], [Debug] and [DynClone].
///
/// The simplest form just requires the typename to be specified. This additionally depends on an
/// implementation of [ExternFn] because after the clause is fully normalized it returns `Self`
/// wrapped in a [Primitive::ExternFn]. It is intended for intermediary
/// stages of the function where validation and the next state are defined in [ExternFn::apply].
///
/// ```
/// atomic_impl!(Multiply1)
/// ```
///
/// The last stage of the function should use the extended form of the macro which takes an
/// additional closure to explicitly describe what happens when the argument is fully processed.
///
/// ```
/// // excerpt from the exact implementation of Multiply
/// atomic_impl!(Multiply0, |Self(a, cls): &Self| {
/// let b: Numeric = cls.clone().try_into().map_err(AssertionError::into_extern)?;
/// Ok(*a * b).into())
/// })
/// ```
///
#[macro_export]
macro_rules! atomic_impl {
($typ:ident) => {
atomic_impl!{$typ, |this: &Self| Ok(Clause::P(Primitive::ExternFn(Box::new(this.clone()))))}
};
($typ:ident, $next_phase:expr) => {
impl Atomic for $typ {
$crate::atomic_defaults!{}
fn run_once(&self) -> Result<Clause, $crate::representations::interpreted::InternalError> {
match <Self as AsRef<Clause>>::as_ref(self).run_once() {
Err(InternalError::NonReducible) => {
($next_phase)(self)
.map_err($crate::representations::interpreted::RuntimeError::Extern)
.map_err(InternalError::Runtime)
}
Ok(arg) => Ok(Clause::P(Primitive::Atom(Atom::new(
<Self as From<(&Self, Clause)>>::from((self, arg))
)))),
Err(e) => Err(e),
}
}
fn run_n_times(&self, n: usize) -> Result<(Clause, usize), $crate::representations::interpreted::RuntimeError> {
match <Self as AsRef<Clause>>::as_ref(self).run_n_times(n) {
Ok((arg, k)) if k == n => Ok((Clause::P(Primitive::Atom(Atom::new(
<Self as From<(&Self, Clause)>>::from((self, arg))))), k)),
Ok((arg, k)) => {
let intermediate = <Self as From<(&Self, Clause)>>::from((self, arg));
($next_phase)(&intermediate)
.map(|cls| (cls, k))
.map_err($crate::representations::interpreted::RuntimeError::Extern)
}
Err(e) => Err(e),
}
}
fn run_to_completion(&self) -> Result<Clause, $crate::representations::interpreted::RuntimeError> {
match <Self as AsRef<Clause>>::as_ref(self).run_to_completion() {
Ok(arg) => {
let intermediate = <Self as From<(&Self, Clause)>>::from((self, arg));
($next_phase)(&intermediate)
.map_err($crate::representations::interpreted::RuntimeError::Extern)
},
Err(e) => Err(e)
}
}
}
};
}
/// Implement the traits required by [atomic_impl] to redirect run_* functions to a field
/// with a particular name.
#[macro_export]
macro_rules! atomic_redirect {
($typ:ident) => {
impl AsRef<Clause> for $typ {
fn as_ref(&self) -> &Clause { &self.0 }
}
impl From<(&Self, Clause)> for $typ {
fn from((old, clause): (&Self, Clause)) -> Self {
Self{ 0: clause, ..old.clone() }
}
}
};
($typ:ident, $field:ident) => {
impl AsRef<Clause> for $typ {
fn as_ref(&self) -> &Clause { &self.$field }
}
impl From<(&Self, Clause)> for $typ {
fn from((old, $field): (&Self, Clause)) -> Self {
Self{ $field, ..old.clone() }
}
}
};
}
/// Implement [ExternFn] with a closure that produces an [Atomic] from a reference to self
/// and a closure. This can be used in conjunction with [atomic_impl] and [atomic_redirect]
/// to normalize the argument automatically before using it.
#[macro_export]
macro_rules! externfn_impl {
($typ:ident, $next_atomic:expr) => {
impl ExternFn for $typ {
fn name(&self) -> &str {stringify!($typ)}
fn apply(&self, c: Clause) -> Result<Clause, Rc<dyn ExternError>> {
match ($next_atomic)(self, c) { // ? casts the result but we want to strictly forward it
Ok(r) => Ok(Clause::P(Primitive::Atom(Atom::new(r)))),
Err(e) => Err(e)
}
}
}
};
}

View File

@@ -0,0 +1,19 @@
#[allow(unused)] // for the doc comments
use crate::foreign::Atomic;
/// A macro that generates the straightforward, syntactically invariant part of implementing
/// [Atomic]. Implemented fns are [Atomic::as_any], [Atomic::definitely_eq] and [Atomic::hash].
///
/// It depends on [Eq] and [Hash]
#[macro_export]
macro_rules! atomic_defaults {
() => {
fn as_any(&self) -> &dyn std::any::Any { self }
fn definitely_eq(&self, _other: &dyn std::any::Any) -> bool {
_other.downcast_ref().map(|o| self == o).unwrap_or(false)
}
fn hash(&self, mut hasher: &mut dyn std::hash::Hasher) {
<Self as std::hash::Hash>::hash(self, &mut hasher)
}
};
}

View File

@@ -0,0 +1,105 @@
#[allow(unused)] // for the doc comments
use crate::representations::Primitive;
#[allow(unused)] // for the doc comments
use crate::foreign::{Atomic, ExternFn};
#[allow(unused)] // for the doc comments
use std::any::Any;
#[allow(unused)] // for the doc comments
use dyn_clone::DynClone;
#[allow(unused)] // for the doc comments
use std::fmt::Debug;
/// A macro that generates implementations of [Atomic] to simplify the development of
/// external bindings for Orchid.
///
/// The macro depends on implementations of [AsRef<Clause>] and [From<(&Self, Clause)>] for
/// extracting the clause to be processed and then reconstructing the [Atomic]. Naturally,
/// supertraits of [Atomic] are also dependencies. These are [Any], [Debug] and [DynClone].
///
/// The simplest form just requires the typename to be specified. This additionally depends on an
/// implementation of [ExternFn] because after the clause is fully normalized it returns `Self`
/// wrapped in a [Primitive::ExternFn]. It is intended for intermediary
/// stages of the function where validation and the next state are defined in [ExternFn::apply].
///
/// ```
/// atomic_impl!(Multiply1)
/// ```
///
/// The last stage of the function should use the extended form of the macro which takes an
/// additional closure to explicitly describe what happens when the argument is fully processed.
///
/// ```
/// // excerpt from the exact implementation of Multiply
/// atomic_impl!(Multiply0, |Self(a, cls): &Self| {
/// let b: Numeric = cls.clone().try_into().map_err(AssertionError::into_extern)?;
/// Ok(*a * b).into())
/// })
/// ```
///
#[macro_export]
macro_rules! atomic_impl {
($typ:ident) => {
atomic_impl!{$typ, |this: &Self| Ok(Clause::P(
$crate::representations::Primitive::ExternFn(Box::new(this.clone()))
))}
};
($typ:ident, $next_phase:expr) => {
impl $crate::foreign::Atomic for $typ {
$crate::atomic_defaults!{}
fn run_once(&self) -> Result<
$crate::representations::interpreted::Clause,
$crate::representations::interpreted::InternalError
> {
match <Self as AsRef<$crate::representations::interpreted::Clause>>::as_ref(self).run_once() {
Err($crate::representations::interpreted::InternalError::NonReducible) => {
($next_phase)(self)
.map_err($crate::representations::interpreted::RuntimeError::Extern)
.map_err($crate::representations::interpreted::InternalError::Runtime)
}
Ok(arg) => Ok($crate::representations::interpreted::Clause::P(
$crate::representations::Primitive::Atom(
$crate::foreign::Atom::new(
<Self as From<(&Self, Clause)>>::from((self, arg))
)
)
)),
Err(e) => Err(e),
}
}
fn run_n_times(&self, n: usize) -> Result<
(
$crate::representations::interpreted::Clause,
usize
),
$crate::representations::interpreted::RuntimeError
> {
match <Self as AsRef<Clause>>::as_ref(self).run_n_times(n) {
Ok((arg, k)) if k == n => Ok((Clause::P(
$crate::representations::Primitive::Atom(
$crate::foreign::Atom::new(
<Self as From<(&Self, Clause)>>::from((self, arg))
)
)
), k)),
Ok((arg, k)) => {
let intermediate = <Self as From<(&Self, Clause)>>::from((self, arg));
($next_phase)(&intermediate)
.map(|cls| (cls, k))
.map_err($crate::representations::interpreted::RuntimeError::Extern)
}
Err(e) => Err(e),
}
}
fn run_to_completion(&self) -> Result<Clause, $crate::representations::interpreted::RuntimeError> {
match <Self as AsRef<Clause>>::as_ref(self).run_to_completion() {
Ok(arg) => {
let intermediate = <Self as From<(&Self, Clause)>>::from((self, arg));
($next_phase)(&intermediate)
.map_err($crate::representations::interpreted::RuntimeError::Extern)
},
Err(e) => Err(e)
}
}
}
};
}

View File

@@ -0,0 +1,48 @@
#[allow(unused)] // for the doc comments
use crate::foreign::Atomic;
#[allow(unused)] // for the doc comments
use std::any::Any;
#[allow(unused)] // for the doc comments
use dyn_clone::DynClone;
#[allow(unused)] // for the doc comments
use std::fmt::Debug;
/// Implement [Atomic] for a structure that cannot be transformed any further. This would be optimal
/// for atomics encapsulating raw data. [Atomic] depends on [Any], [Debug] and [DynClone].
#[macro_export]
macro_rules! atomic_inert {
($typ:ident) => {
impl $crate::foreign::Atomic for $typ {
$crate::atomic_defaults!{}
fn run_once(&self) -> Result<
$crate::representations::interpreted::Clause,
$crate::representations::interpreted::InternalError
> {
Err($crate::representations::interpreted::InternalError::NonReducible)
}
fn run_n_times(&self, _: usize) -> Result<
(
$crate::representations::interpreted::Clause,
usize
),
$crate::representations::interpreted::RuntimeError
> {
Ok(($crate::representations::interpreted::Clause::P(
$crate::representations::Primitive::Atom(
$crate::foreign::Atom::new(self.clone())
)
), 0))
}
fn run_to_completion(&self) -> Result<
$crate::representations::interpreted::Clause,
$crate::representations::interpreted::RuntimeError
> {
Ok($crate::representations::interpreted::Clause::P(
$crate::representations::Primitive::Atom(
$crate::foreign::Atom::new(self.clone())
)
))
}
}
};
}

View File

@@ -0,0 +1,28 @@
#[allow(unused)]
use super::atomic_impl;
/// Implement the traits required by [atomic_impl] to redirect run_* functions to a field
/// with a particular name.
#[macro_export]
macro_rules! atomic_redirect {
($typ:ident) => {
impl AsRef<Clause> for $typ {
fn as_ref(&self) -> &Clause { &self.0 }
}
impl From<(&Self, Clause)> for $typ {
fn from((old, clause): (&Self, Clause)) -> Self {
Self{ 0: clause, ..old.clone() }
}
}
};
($typ:ident, $field:ident) => {
impl AsRef<Clause> for $typ {
fn as_ref(&self) -> &Clause { &self.$field }
}
impl From<(&Self, Clause)> for $typ {
fn from((old, $field): (&Self, Clause)) -> Self {
Self{ $field, ..old.clone() }
}
}
};
}

View File

@@ -0,0 +1,43 @@
#[allow(unused)] // for the doc comments
use crate::{atomic_impl, atomic_redirect};
#[allow(unused)] // for the doc comments
use crate::representations::Primitive;
#[allow(unused)] // for the doc comments
use crate::foreign::{Atomic, ExternFn};
#[allow(unused)] // for the doc comments
use std::any::Any;
#[allow(unused)] // for the doc comments
use std::hash::Hash;
#[allow(unused)] // for the doc comments
use dyn_clone::DynClone;
#[allow(unused)] // for the doc comments
use std::fmt::Debug;
/// Implement [ExternFn] with a closure that produces an [Atomic] from a reference to self
/// and a closure. This can be used in conjunction with [atomic_impl] and [atomic_redirect]
/// to normalize the argument automatically before using it.
#[macro_export]
macro_rules! externfn_impl {
($typ:ident, $next_atomic:expr) => {
impl $crate::foreign::ExternFn for $typ {
fn name(&self) -> &str {stringify!($typ)}
fn apply(&self,
c: $crate::representations::interpreted::Clause
) -> Result<
$crate::representations::interpreted::Clause,
std::rc::Rc<dyn $crate::foreign::ExternError>
> {
match ($next_atomic)(self, c) { // ? casts the result but we want to strictly forward it
Ok(r) => Ok(
$crate::representations::interpreted::Clause::P(
$crate::representations::Primitive::Atom(
$crate::foreign::Atom::new(r)
)
)
),
Err(e) => Err(e)
}
}
}
};
}

View File

@@ -0,0 +1,5 @@
mod atomic_defaults;
mod atomic_impl;
mod atomic_inert;
mod atomic_redirect;
mod externfn_impl;

View File

@@ -8,12 +8,13 @@
#![feature(arc_unwrap_or_clone)] #![feature(arc_unwrap_or_clone)]
#![feature(hasher_prefixfree_extras)] #![feature(hasher_prefixfree_extras)]
#![feature(closure_lifetime_binder)] #![feature(closure_lifetime_binder)]
#![feature(generic_arg_infer)]
use std::{env::current_dir, io}; use std::{env::current_dir, collections::HashMap};
// mod executor; // mod executor;
mod parse; mod parse;
mod project; pub(crate) mod project;
mod utils; mod utils;
mod representations; mod representations;
mod rule; mod rule;
@@ -21,16 +22,17 @@ mod scheduler;
pub(crate) mod foreign; pub(crate) mod foreign;
mod external; mod external;
mod foreign_macros; mod foreign_macros;
use file_loader::LoadingError;
pub use representations::ast; pub use representations::ast;
use ast::{Expr, Clause}; use ast::{Expr, Clause};
// use representations::typed as t; // use representations::typed as t;
use mappable_rc::Mrc; use mappable_rc::Mrc;
use project::{rule_collector, Loaded, file_loader}; use project::{rule_collector, file_loader};
use rule::Repository; use rule::Repository;
use utils::{to_mrc_slice, mrc_empty_slice, one_mrc_slice}; use utils::to_mrc_slice;
use crate::representations::{ast_to_postmacro, postmacro_to_interpreted, interpreted}; use crate::external::std::std;
use crate::project::{map_loader, string_loader, Loader, ModuleError};
use crate::representations::{ast_to_postmacro, postmacro_to_interpreted};
fn literal(orig: &[&str]) -> Mrc<[String]> { fn literal(orig: &[&str]) -> Mrc<[String]> {
to_mrc_slice(vliteral(orig)) to_mrc_slice(vliteral(orig))
@@ -41,60 +43,68 @@ fn vliteral(orig: &[&str]) -> Vec<String> {
} }
static PRELUDE:&str = r#" static PRELUDE:&str = r#"
export ... $name =1000=> (match_seqence $name) import std::(
export ] =1000=> conslist_carriage(none) num::(add, subtract, multiply, remainder, divide),
export , $name conslist_carriage($tail) =1000=> conslist_carriage((some (cons $name $tail))) bool::(equals, ifthenelse),
export [ $name conslist_carriage($tail) =1000=> (some (cons $name $tail)) str::concatenate
export (match_sequence $lhs) >> (match_sequence $rhs) =100=> (bind ($lhs) (\_. $rhs)) )
export (match_sequence $lhs) >>= (match_sequence $rhs) =100=> (bind ($lhs) ($rhs))
"#;
export (...$a + ...$b) =1001=> (add (...$a) (...$b))
export (...$a - ...$b:1) =1001=> (subtract (...$a) (...$b))
export (...$a * ...$b) =1000=> (multiply (...$a) (...$b))
export (...$a % ...$b:1) =1000=> (remainder (...$a) (...$b))
export (...$a / ...$b:1) =1000=> (divide (...$a) (...$b))
export (...$a = ...$b) =1002=> (equals (...$a) (...$b))
export (...$a ++ ...$b) =1003=> (concatenate (...$a) (...$b))
export do { ...$statement ; ...$rest:1 } =10_001=> (
statement (...$statement) do { ...$rest }
)
export do { ...$statement } =10_000=> (...$statement)
export statement (let $_name = ...$value) ...$next =10_000=> (
(\$_name. ...$next) (...$value)
)
export statement (cps $_name = ...$operation) ...$next =10_001=> (
(...$operation) \$_name. ...$next
)
export statement (cps ...$operation) ...$next =10_000=> (
(...$operation) (...$next)
)
export if ...$cond then ...$true else ...$false:1 =5_000=> (
ifthenelse (...$cond) (...$true) (...$false)
)
"#;
fn initial_tree() -> Mrc<[Expr]> { fn initial_tree() -> Mrc<[Expr]> {
to_mrc_slice(vec![Expr(Clause::Name { to_mrc_slice(vec![Expr(Clause::Name {
local: None, local: None,
qualified: literal(&["main", "main"]) qualified: literal(&["mod", "main", "main"])
}, to_mrc_slice(vec![]))]) }, to_mrc_slice(vec![]))])
} }
#[allow(unused)]
// fn typed_notation_debug() {
// let true_ex = t::Clause::Auto(0, mrc_empty_slice(),
// t::Clause::Lambda(1, one_mrc_slice(t::Clause::AutoArg(0)),
// t::Clause::Lambda(2, one_mrc_slice(t::Clause::AutoArg(0)),
// t::Clause::LambdaArg(1).wrap_t(t::Clause::AutoArg(0))
// ).wrap()
// ).wrap()
// ).wrap();
// let false_ex = t::Clause::Auto(0, mrc_empty_slice(),
// t::Clause::Lambda(1, one_mrc_slice(t::Clause::AutoArg(0)),
// t::Clause::Lambda(2, one_mrc_slice(t::Clause::AutoArg(0)),
// t::Clause::LambdaArg(2).wrap_t(t::Clause::AutoArg(0))
// ).wrap()
// ).wrap()
// ).wrap();
// println!("{:?}", t::Clause::Apply(t::Clause::Apply(Mrc::clone(&true_ex), true_ex).wrap(), false_ex))
// }
#[allow(unused)] #[allow(unused)]
fn load_project() { fn load_project() {
let cwd = current_dir().unwrap(); let collect_rules = rule_collector(map_loader(HashMap::from([
let collect_rules = rule_collector(move |n| -> Result<Loaded, LoadingError> { ("std", std().boxed()),
if n == literal(&["prelude"]) { Ok(Loaded::Module(PRELUDE.to_string())) } ("prelude", string_loader(PRELUDE).boxed()),
else { file_loader(cwd.clone())(n) } ("mod", file_loader(current_dir().expect("Missing CWD!")).boxed())
}, vliteral(&["...", ">>", ">>=", "[", "]", ",", "=", "=>"])); ])));
let rules = match collect_rules.try_find(&literal(&["main"])) { let rules = match collect_rules.try_find(&literal(&["mod", "main"])) {
Ok(rules) => rules, Ok(rules) => rules,
Err(err) => panic!("{:#?}", err) Err(err) => if let ModuleError::Syntax(pe) = err {
panic!("{}", pe);
} else {panic!("{:#?}", err)}
}; };
let mut tree = initial_tree(); let mut tree = initial_tree();
println!("Start processing {tree:?}"); println!("Start processing {tree:?}");
let repo = Repository::new(rules.as_ref().to_owned()); let repo = Repository::new(rules.as_ref().to_owned());
println!("Ruleset: {repo:?}"); println!("Ruleset: {repo:?}");
xloop!(let mut i = 0; i < 10; i += 1; { xloop!(let mut i = 0; i < 100; i += 1; {
match repo.step(Mrc::clone(&tree)) { match repo.step(Mrc::clone(&tree)) {
Ok(Some(phase)) => { Ok(Some(phase)) => {
println!("Step {i}: {phase:?}"); //println!("Step {i}: {phase:?}");
tree = phase; tree = phase;
}, },
Ok(None) => { Ok(None) => {
@@ -116,4 +126,9 @@ fn load_project() {
fn main() { fn main() {
// lambda_notation_debug(); // lambda_notation_debug();
load_project(); load_project();
// let mut std = std();
// match std.load(&["parse_float"]) {
// Ok(_) => println!("wtf"),
// Err(e) => panic!("{:?}", e)
// }
} }

View File

@@ -93,14 +93,14 @@ pub fn xpr_parser() -> impl Parser<Lexeme, Expr, Error = Simple<Lexeme>> {
let clause = let clause =
enum_parser!(Lexeme::Comment).repeated() enum_parser!(Lexeme::Comment).repeated()
.ignore_then(choice(( .ignore_then(choice((
enum_parser!(Lexeme >> Literal; Int, Num, Char, Str).map(Primitive::Literal).map(Clause::P), enum_parser!(Lexeme >> Literal; Uint, Num, Char, Str).map(Primitive::Literal).map(Clause::P),
placeholder_parser().map(|key| Clause::Placeh{key, vec: None}), placeholder_parser().map(|key| Clause::Placeh{key, vec: None}),
just(Lexeme::name("...")).to(true) just(Lexeme::name("...")).to(true)
.or(just(Lexeme::name("..")).to(false)) .or(just(Lexeme::name("..")).to(false))
.then(placeholder_parser()) .then(placeholder_parser())
.then( .then(
just(Lexeme::Type) just(Lexeme::Type)
.ignore_then(enum_parser!(Lexeme::Int)) .ignore_then(enum_parser!(Lexeme::Uint))
.or_not().map(Option::unwrap_or_default) .or_not().map(Option::unwrap_or_default)
) )
.map(|((nonzero, key), prio)| Clause::Placeh{key, vec: Some(( .map(|((nonzero, key), prio)| Clause::Placeh{key, vec: Some((

View File

@@ -24,7 +24,7 @@ impl From<Entry> for (Lexeme, Range<usize>) {
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub enum Lexeme { pub enum Lexeme {
Num(NotNan<f64>), Num(NotNan<f64>),
Int(u64), Uint(u64),
Char(char), Char(char),
Str(String), Str(String),
Name(String), Name(String),
@@ -35,14 +35,16 @@ pub enum Lexeme {
BS, // Backslash BS, // Backslash
At, At,
Type, // type operator Type, // type operator
Comment(String) Comment(String),
Export,
Import,
} }
impl Debug for Lexeme { impl Debug for Lexeme {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Num(n) => write!(f, "{}", n), Self::Num(n) => write!(f, "{}", n),
Self::Int(i) => write!(f, "{}", i), Self::Uint(i) => write!(f, "{}", i),
Self::Char(c) => write!(f, "{:?}", c), Self::Char(c) => write!(f, "{:?}", c),
Self::Str(s) => write!(f, "{:?}", s), Self::Str(s) => write!(f, "{:?}", s),
Self::Name(name) => write!(f, "{}", name), Self::Name(name) => write!(f, "{}", name),
@@ -59,6 +61,8 @@ impl Debug for Lexeme {
Self::At => write!(f, "@"), Self::At => write!(f, "@"),
Self::Type => write!(f, ":"), Self::Type => write!(f, ":"),
Self::Comment(text) => write!(f, "--[{}]--", text), Self::Comment(text) => write!(f, "--[{}]--", text),
Self::Export => write!(f, "export"),
Self::Import => write!(f, "import"),
} }
} }
} }
@@ -118,11 +122,14 @@ fn paren_parser<'a>(
}) })
} }
pub fn lexer<'a, T: 'a>(ops: &[T]) -> impl Parser<char, LexedText, Error=Simple<char>> + 'a pub fn lexer<'a, T: 'a>(ops: &[T]) -> impl Parser<char, Vec<Entry>, Error=Simple<char>> + 'a
where T: AsRef<str> + Clone { where T: AsRef<str> + Clone {
let all_ops = ops.iter().map(|o| o.as_ref().to_string()) let all_ops = ops.iter().map(|o| o.as_ref().to_string())
.chain(iter::once(".".to_string())).collect::<Vec<_>>(); .chain([",", ".", "..", "..."].into_iter().map(str::to_string))
recursive(move |recurse: Recursive<char, LexSubres, Simple<char>>| { .collect::<Vec<_>>();
just("export").padded().to(Lexeme::Export)
.or(just("import").padded().to(Lexeme::Import))
.or_not().then(recursive(move |recurse: Recursive<char, LexSubres, Simple<char>>| {
choice(( choice((
paren_parser(recurse.clone(), '(', ')'), paren_parser(recurse.clone(), '(', ')'),
paren_parser(recurse.clone(), '[', ']'), paren_parser(recurse.clone(), '[', ']'),
@@ -135,7 +142,7 @@ where T: AsRef<str> + Clone {
just('\\').padded().to(Lexeme::BS), just('\\').padded().to(Lexeme::BS),
just('@').padded().to(Lexeme::At), just('@').padded().to(Lexeme::At),
just(':').to(Lexeme::Type), just(':').to(Lexeme::Type),
number::int_parser().map(Lexeme::Int), // all ints are valid floats so it takes precedence number::int_parser().map(Lexeme::Uint), // all ints are valid floats so it takes precedence
number::float_parser().map(Lexeme::Num), number::float_parser().map(Lexeme::Num),
string::char_parser().map(Lexeme::Char), string::char_parser().map(Lexeme::Char),
string::str_parser().map(Lexeme::Str), string::str_parser().map(Lexeme::Str),
@@ -143,7 +150,9 @@ where T: AsRef<str> + Clone {
)).map_with_span(|lx, span| box_once(Entry(lx, span)) as LexSubres) )).map_with_span(|lx, span| box_once(Entry(lx, span)) as LexSubres)
)) ))
}).separated_by(one_of("\t ").repeated()) }).separated_by(one_of("\t ").repeated())
.flatten().collect() .flatten().collect())
.separated_by(just('\n').then(text::whitespace()).ignored()) .map(|(prefix, rest): (Option<Lexeme>, Vec<Entry>)| {
.map(LexedText) prefix.into_iter().map(|l| Entry(l, 0..6)).chain(rest.into_iter()).collect()
})
.then_ignore(text::whitespace()).then_ignore(end())
} }

View File

@@ -16,3 +16,5 @@ pub use sourcefile::exported_names;
pub use lexer::{lexer, Lexeme, Entry as LexerEntry}; pub use lexer::{lexer, Lexeme, Entry as LexerEntry};
pub use name::is_op; pub use name::is_op;
pub use parse::{parse, reparse, ParseError}; pub use parse::{parse, reparse, ParseError};
pub use import::Import;
pub use number::{float_parser, int_parser};

View File

@@ -4,7 +4,7 @@ use chumsky::{prelude::{Simple, end}, Stream, Parser};
use itertools::Itertools; use itertools::Itertools;
use thiserror::Error; use thiserror::Error;
use crate::{ast::Rule, parse::lexer::LexedText}; use crate::{ast::Rule, parse::{lexer::LexedText, sourcefile::split_lines}};
use super::{Lexeme, FileEntry, lexer, line_parser, LexerEntry}; use super::{Lexeme, FileEntry, lexer, line_parser, LexerEntry};
@@ -17,14 +17,13 @@ pub enum ParseError {
Ast(Vec<Simple<Lexeme>>) Ast(Vec<Simple<Lexeme>>)
} }
pub fn parse<'a, Iter, S, Op>(ops: &[Op], stream: S) -> Result<Vec<FileEntry>, ParseError> pub fn parse<'a, Op>(ops: &[Op], data: &str) -> Result<Vec<FileEntry>, ParseError>
where where Op: 'a + AsRef<str> + Clone {
Op: 'a + AsRef<str> + Clone, let lexie = lexer(ops);
Iter: Iterator<Item = (char, Range<usize>)> + 'a, let token_batchv = split_lines(data).map(|line| {
S: Into<Stream<'a, char, Range<usize>, Iter>> { lexie.parse(line).map_err(ParseError::Lex)
let lexed = lexer(ops).parse(stream).map_err(ParseError::Lex)?; }).collect::<Result<Vec<_>, _>>()?;
println!("Lexed:\n{:?}", lexed); println!("Lexed:\n{:?}", LexedText(token_batchv.clone()));
let LexedText(token_batchv) = lexed;
let parsr = line_parser().then_ignore(end()); let parsr = line_parser().then_ignore(end());
let (parsed_lines, errors_per_line) = token_batchv.into_iter().filter(|v| { let (parsed_lines, errors_per_line) = token_batchv.into_iter().filter(|v| {
!v.is_empty() !v.is_empty()
@@ -34,7 +33,7 @@ where
// Stream expects tuples, lexer outputs structs // Stream expects tuples, lexer outputs structs
let tuples = v.into_iter().map_into::<(Lexeme, Range<usize>)>(); let tuples = v.into_iter().map_into::<(Lexeme, Range<usize>)>();
parsr.parse(Stream::from_iter(end..end+1, tuples)) parsr.parse(Stream::from_iter(end..end+1, tuples))
// ^^^^^^^^^^ // ^^^^^^^^^^
// I haven't the foggiest idea why this is needed, parsers are supposed to be lazy so the // I haven't the foggiest idea why this is needed, parsers are supposed to be lazy so the
// end of input should make little difference // end of input should make little difference
}).map(|res| match res { }).map(|res| match res {
@@ -48,13 +47,10 @@ where
else { Ok(parsed_lines.into_iter().map(Option::unwrap).collect()) } else { Ok(parsed_lines.into_iter().map(Option::unwrap).collect()) }
} }
pub fn reparse<'a, Iter, S, Op>(ops: &[Op], stream: S, pre: &[FileEntry]) pub fn reparse<'a, Op>(ops: &[Op], data: &str, pre: &[FileEntry])
-> Result<Vec<FileEntry>, ParseError> -> Result<Vec<FileEntry>, ParseError>
where where Op: 'a + AsRef<str> + Clone {
Op: 'a + AsRef<str> + Clone, let result = parse(ops, data)?;
Iter: Iterator<Item = (char, Range<usize>)> + 'a,
S: Into<Stream<'a, char, Range<usize>, Iter>> {
let result = parse(ops, stream)?;
Ok(result.into_iter().zip(pre.iter()).map(|(mut output, donor)| { Ok(result.into_iter().zip(pre.iter()).map(|(mut output, donor)| {
if let FileEntry::Rule(Rule{source, ..}, _) = &mut output { if let FileEntry::Rule(Rule{source, ..}, _) = &mut output {
if let FileEntry::Rule(Rule{source: s2, ..}, _) = donor { if let FileEntry::Rule(Rule{source: s2, ..}, _) = donor {

View File

@@ -1,23 +1,27 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::iter;
use crate::{enum_parser, box_chain}; use crate::{enum_parser, box_chain};
use crate::ast::{Expr, Clause, Rule}; use crate::ast::{Expr, Clause, Rule};
use crate::utils::to_mrc_slice; use crate::utils::{to_mrc_slice, one_mrc_slice};
use crate::utils::Stackframe; use crate::utils::Stackframe;
use crate::utils::iter::box_empty; use crate::utils::iter::box_empty;
use super::expression::xpr_parser; use super::expression::xpr_parser;
use super::import; use super::import::{self, Import};
use super::import::import_parser; use super::import::import_parser;
use super::lexer::Lexeme; use super::lexer::Lexeme;
use chumsky::{Parser, prelude::*}; use chumsky::{Parser, prelude::*};
use ordered_float::NotNan; use ordered_float::NotNan;
use lazy_static::lazy_static;
/// Anything we might encounter in a file /// Anything we might encounter in a file
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum FileEntry { pub enum FileEntry {
Import(Vec<import::Import>), Import(Vec<import::Import>),
Comment(String), Comment(String),
/// The bool indicates whether the rule is exported - whether tokens uniquely defined inside it
/// should be exported
Rule(Rule, bool), Rule(Rule, bool),
Export(Vec<Vec<String>>) Export(Vec<Vec<String>>)
} }
@@ -103,10 +107,10 @@ pub fn line_parser() -> impl Parser<Lexeme, FileEntry, Error = Simple<Lexeme>> {
choice(( choice((
// In case the usercode wants to parse doc // In case the usercode wants to parse doc
enum_parser!(Lexeme >> FileEntry; Comment), enum_parser!(Lexeme >> FileEntry; Comment),
just(Lexeme::name("import")) just(Lexeme::Import)
.ignore_then(import_parser().map(FileEntry::Import)) .ignore_then(import_parser().map(FileEntry::Import))
.then_ignore(enum_parser!(Lexeme::Comment)), .then_ignore(enum_parser!(Lexeme::Comment).or_not()),
just(Lexeme::name("export")).map_err_with_span(|e, s| { just(Lexeme::Export).map_err_with_span(|e, s| {
println!("{:?} could not yield an export", s); e println!("{:?} could not yield an export", s); e
}).ignore_then( }).ignore_then(
just(Lexeme::NS).ignore_then( just(Lexeme::NS).ignore_then(
@@ -114,13 +118,14 @@ pub fn line_parser() -> impl Parser<Lexeme, FileEntry, Error = Simple<Lexeme>> {
.separated_by(just(Lexeme::name(","))) .separated_by(just(Lexeme::name(",")))
.delimited_by(just(Lexeme::LP('(')), just(Lexeme::RP('('))) .delimited_by(just(Lexeme::LP('(')), just(Lexeme::RP('(')))
).map(FileEntry::Export) ).map(FileEntry::Export)
).or(rule_parser().map(|(source, prio, target)| { .or(rule_parser().map(|(source, prio, target)| {
FileEntry::Rule(Rule { FileEntry::Rule(Rule {
source: to_mrc_slice(source), source: to_mrc_slice(source),
prio, prio,
target: to_mrc_slice(target) target: to_mrc_slice(target)
}, true) }, true)
})), }))
),
// This could match almost anything so it has to go last // This could match almost anything so it has to go last
rule_parser().map(|(source, prio, target)| FileEntry::Rule(Rule{ rule_parser().map(|(source, prio, target)| FileEntry::Rule(Rule{
source: to_mrc_slice(source), source: to_mrc_slice(source),
@@ -153,3 +158,24 @@ where I: Iterator<Item = &'b FileEntry> + 'a {
_ => None _ => None
}).flatten() }).flatten()
} }
pub fn split_lines(data: &str) -> impl Iterator<Item = &str> {
let mut source = data.char_indices();
let mut last_slice = 0;
iter::from_fn(move || {
let mut paren_count = 0;
while let Some((i, c)) = source.next() {
match c {
'(' | '{' | '[' => paren_count += 1,
')' | '}' | ']' => paren_count -= 1,
'\n' if paren_count == 0 => {
let begin = last_slice;
last_slice = i;
return Some(&data[begin..i]);
},
_ => (),
}
}
None
})
}

View File

@@ -1,5 +0,0 @@
#[derive(Debug, Clone)]
pub enum Loaded {
Module(String),
Namespace(Vec<String>),
}

View File

@@ -0,0 +1,7 @@
use crate::parse::FileEntry;
use super::{Loader, Loaded};
pub fn ext_loader(data: Vec<FileEntry>) -> impl Loader {
move |_: &[&str]| Ok(Loaded::External(data.clone()))
}

View File

@@ -1,27 +1,10 @@
use std::io;
use std::rc::Rc;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::path::PathBuf; use std::path::PathBuf;
use mappable_rc::Mrc; use super::{Loaded, Loader, LoadingError};
use super::loaded::Loaded; pub fn file_loader(proj: PathBuf) -> impl Loader + 'static {
move |path: &[&str]| {
#[derive(Clone, Debug)]
pub enum LoadingError {
IOErr(Rc<io::Error>),
UnknownNode(String),
Missing(String)
}
impl From<io::Error> for LoadingError {
fn from(inner: io::Error) -> Self {
LoadingError::IOErr(Rc::new(inner))
}
}
pub fn file_loader(proj: PathBuf) -> impl FnMut(Mrc<[String]>) -> Result<Loaded, LoadingError> + 'static {
move |path| {
let dirpath = proj.join(path.join("/")); let dirpath = proj.join(path.join("/"));
if dirpath.is_dir() || dirpath.is_symlink() { if dirpath.is_dir() || dirpath.is_symlink() {
return Ok(Loaded::Namespace( return Ok(Loaded::Namespace(

View File

@@ -0,0 +1,23 @@
use itertools::Itertools;
use ordered_float::NotNan;
use crate::parse::FileEntry;
use crate::representations::Primitive;
use crate::utils::{one_mrc_slice, mrc_empty_slice};
use crate::foreign::ExternFn;
use crate::ast::{Rule, Expr, Clause};
use super::{Loader, ext_loader};
pub fn fnlib_loader(src: Vec<(&'static str, Box<dyn ExternFn>)>) -> impl Loader {
let entries = src.into_iter().map(|(name, xfn)| FileEntry::Rule(Rule {
source: one_mrc_slice(Expr(Clause::Name{
local: Some(name.to_string()),
qualified: one_mrc_slice(name.to_string())
}, mrc_empty_slice())),
prio: NotNan::try_from(0.0f64).unwrap(),
target: one_mrc_slice(Expr(Clause::P(Primitive::ExternFn(xfn)), mrc_empty_slice()))
}, true))
.collect_vec();
ext_loader(entries)
}

View File

@@ -0,0 +1,16 @@
use std::collections::HashMap;
use super::{Loader, LoadingError, Loaded};
pub fn map_loader<'a, T: Loader + 'a>(mut map: HashMap<&'a str, T>) -> impl Loader + 'a {
move |path: &[&str]| {
let (key, subpath) = if let Some(sf) = path.split_first() {sf}
else {return Ok(Loaded::Module(map.keys().cloned().collect()))};
let sub = if let Some(sub) = map.get_mut(key.to_string().as_str()) {sub}
else {return Err(
if subpath.len() == 0 {LoadingError::UnknownNode(path.join("::"))}
else {LoadingError::Missing(path.join("::"))}
)};
sub.load(subpath)
}
}

View File

@@ -0,0 +1,61 @@
mod file_loader;
mod ext_loader;
mod string_loader;
mod map_loader;
mod fnlib_loader;
mod overlay_loader;
mod prefix_loader;
pub use file_loader::file_loader;
pub use ext_loader::ext_loader;
pub use fnlib_loader::fnlib_loader;
pub use string_loader::string_loader;
pub use map_loader::map_loader;
pub use overlay_loader::overlay_loader;
pub use prefix_loader::prefix_loader;
use std::{rc::Rc, io};
use crate::parse::FileEntry;
#[derive(Clone, Debug)]
pub enum LoadingError {
/// An IO operation has failed (i.e. no read permission)
IOErr(Rc<io::Error>),
/// The leaf does not exist
UnknownNode(String),
/// The leaf and at least the immediately containing namespace don't exist
Missing(String)
}
impl From<io::Error> for LoadingError {
fn from(inner: io::Error) -> Self {
LoadingError::IOErr(Rc::new(inner))
}
}
#[derive(Debug, Clone)]
pub enum Loaded {
Module(String),
Namespace(Vec<String>),
External(Vec<FileEntry>)
}
pub trait Loader {
fn load<'s, 'a>(&'s mut self, path: &'a [&'a str]) -> Result<Loaded, LoadingError>;
fn boxed<'a>(self) -> Box<dyn 'a + Loader> where Self: 'a + Sized {
Box::new(self)
}
}
impl<T> Loader for T where T: for<'a> FnMut(&'a [&'a str]) -> Result<Loaded, LoadingError> {
fn load(&mut self, path: &[&str]) -> Result<Loaded, LoadingError> {
(self)(path)
}
}
impl Loader for Box<dyn Loader> {
fn load<'s, 'a>(&'s mut self, path: &'a [&'a str]) -> Result<Loaded, LoadingError> {
self.as_mut().load(path)
}
}

View File

@@ -0,0 +1,19 @@
use super::{Loader, LoadingError};
pub fn overlay_loader(mut base: impl Loader, mut overlay: impl Loader) -> impl Loader {
move |path: &[&str]| match overlay.load(path) {
ok@Ok(_) => ok,
e@Err(LoadingError::IOErr(_)) => e,
Err(_) => base.load(path)
}
}
#[macro_export]
macro_rules! overlay_loader {
($left:expr, $right:expr) => {
overlay_loader($left, $right)
};
($left:expr, $mid:expr, $($rest:expr),+) => {
overlay_loader($left, overlay_loader!($mid, $($rest),+))
};
}

View File

@@ -0,0 +1,10 @@
use super::Loader;
pub fn prefix_loader<'a>(
prefix: &'a [&'a str], mut loader: impl Loader + 'a
) -> impl Loader + 'a {
move |path: &[&str]| {
let full_path = prefix.iter().chain(path.iter()).map(|s| s.to_string()).clone();
loader.load(path)
}
}

View File

@@ -0,0 +1,5 @@
use super::{Loader, Loaded};
pub fn string_loader<'a>(data: &'a str) -> impl Loader + 'a {
move |_: &[&str]| Ok(Loaded::Module(data.to_string()))
}

View File

@@ -1,10 +1,14 @@
mod rule_collector; mod rule_collector;
pub use rule_collector::rule_collector; mod loading;
mod prefix; mod prefix;
mod name_resolver; mod name_resolver;
mod loaded;
pub use loaded::Loaded;
mod module_error; mod module_error;
pub mod file_loader;
pub use file_loader::file_loader; pub use module_error::ModuleError;
pub use rule_collector::rule_collector;
pub use loading::{
Loader, Loaded, LoadingError,
ext_loader, file_loader, string_loader, map_loader, fnlib_loader,
overlay_loader, prefix_loader
};
use crate::ast::Rule; use crate::ast::Rule;

View File

@@ -3,49 +3,47 @@ use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt::Debug; use std::fmt::Debug;
use std::rc::Rc; use std::rc::Rc;
use itertools::Itertools;
use mappable_rc::Mrc; use mappable_rc::Mrc;
use crate::ast::Rule; use crate::ast::Rule;
use crate::parse::{self, FileEntry}; use crate::parse::{self, FileEntry};
use crate::utils::{Cache, mrc_derive, to_mrc_slice}; use crate::utils::{Cache, mrc_derive, to_mrc_slice, one_mrc_slice};
use super::name_resolver::NameResolver; use super::name_resolver::NameResolver;
use super::module_error::ModuleError; use super::module_error::ModuleError;
use super::prefix::prefix_expr; use super::prefix::prefix_expr;
use super::loaded::Loaded; use super::loading::{Loaded, Loader, LoadingError};
use crate::parse::Import;
type ParseResult<T, ELoad> = Result<T, ModuleError<ELoad>>; type ParseResult<T> = Result<T, ModuleError<LoadingError>>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Module { pub struct Module {
pub rules: Vec<Rule>, pub rules: Vec<Rule>,
pub exports: Vec<String>, pub exports: Vec<String>,
pub references: Vec<Mrc<[String]>> pub references: HashSet<Mrc<[String]>>
} }
pub type RuleCollectionResult<ELoad> = Result<Vec<super::Rule>, ModuleError<ELoad>>; pub type RuleCollectionResult = Result<Vec<super::Rule>, ModuleError<LoadingError>>;
pub fn rule_collector<F: 'static, ELoad>( pub fn rule_collector<F: 'static>(
load_mod: F, load_mod: F
prelude: Vec<String> ) -> Cache<'static, Mrc<[String]>, RuleCollectionResult>
) -> Cache<'static, Mrc<[String]>, RuleCollectionResult<ELoad>> where F: Loader
where
F: FnMut(Mrc<[String]>) -> Result<Loaded, ELoad>,
ELoad: Clone + Debug
{ {
let load_mod_rc = RefCell::new(load_mod); let load_mod_rc = RefCell::new(load_mod);
// Map paths to a namespace with name list (folder) or module with source text (file) // Map paths to a namespace with name list (folder) or module with source text (file)
let loaded = Rc::new(Cache::new(move |path: Mrc<[String]>, _| let loaded = Rc::new(Cache::new(move |path: Mrc<[String]>, _| -> ParseResult<Loaded> {
-> ParseResult<Loaded, ELoad> { load_mod_rc.borrow_mut().load(&path.iter().map(|s| s.as_str()).collect_vec()).map_err(ModuleError::Load)
(load_mod_rc.borrow_mut())(path).map_err(ModuleError::Load)
})); }));
// Map names to the longest prefix that points to a valid module // Map names to the longest prefix that points to a valid module
// At least one segment must be in the prefix, and the prefix must not be the whole name // At least one segment must be in the prefix, and the prefix must not be the whole name
let modname = Rc::new(Cache::new({ let modname = Rc::new(Cache::new({
let loaded = Rc::clone(&loaded); let loaded = loaded.clone();
move |symbol: Mrc<[String]>, _| -> Result<Mrc<[String]>, Vec<ModuleError<ELoad>>> { move |symbol: Mrc<[String]>, _| -> Result<Mrc<[String]>, Vec<ModuleError<LoadingError>>> {
let mut errv: Vec<ModuleError<ELoad>> = Vec::new(); let mut errv: Vec<ModuleError<LoadingError>> = Vec::new();
let reg_err = |e, errv: &mut Vec<ModuleError<ELoad>>| { let reg_err = |e, errv: &mut Vec<ModuleError<LoadingError>>| {
errv.push(e); errv.push(e);
if symbol.len() == errv.len() { Err(errv.clone()) } if symbol.len() == errv.len() { Err(errv.clone()) }
else { Ok(()) } else { Ok(()) }
@@ -54,8 +52,8 @@ where
let path = mrc_derive(&symbol, |s| &s[..s.len() - errv.len() - 1]); let path = mrc_derive(&symbol, |s| &s[..s.len() - errv.len() - 1]);
match loaded.try_find(&path) { match loaded.try_find(&path) {
Ok(imports) => match imports.as_ref() { Ok(imports) => match imports.as_ref() {
Loaded::Module(_) => break Ok(path), Loaded::Module(_) | Loaded::External(_) => break Ok(path),
_ => reg_err(ModuleError::None, &mut errv)? Loaded::Namespace(_) => reg_err(ModuleError::None, &mut errv)?
}, },
Err(err) => reg_err(err, &mut errv)? Err(err) => reg_err(err, &mut errv)?
} }
@@ -63,21 +61,33 @@ where
} }
})); }));
// Preliminarily parse a file, substitution rules and imports are valid // Preliminarily parse a file, substitution rules and imports are valid
let prelude_path = one_mrc_slice("prelude".to_string());
let preparsed = Rc::new(Cache::new({ let preparsed = Rc::new(Cache::new({
let loaded = Rc::clone(&loaded); let loaded = loaded.clone();
let prelude2 = prelude.clone(); move |path: Mrc<[String]>, _| -> ParseResult<Vec<FileEntry>> {
move |path: Mrc<[String]>, _| -> ParseResult<Vec<FileEntry>, ELoad> {
let loaded = loaded.try_find(&path)?; let loaded = loaded.try_find(&path)?;
if let Loaded::Module(source) = loaded.as_ref() { match loaded.as_ref() {
Ok(parse::parse(&prelude2, source.as_str())?) Loaded::Module(source) => {
} else {Err(ModuleError::None)} let mut entv = parse::parse(&[] as &[&str], source.as_str())?;
if !entv.iter().any(|ent| if let FileEntry::Import(imps) = ent {
imps.iter().any(|imp| imp.path.starts_with(&prelude_path))
} else {false}) && path != prelude_path {
entv.push(FileEntry::Import(vec![Import{
name: None, path: Mrc::clone(&prelude_path)
}]))
}
Ok(entv)
}
Loaded::External(ast) => Ok(ast.clone()),
Loaded::Namespace(_) => Err(ModuleError::None),
}
} }
})); }));
// Collect all toplevel names exported from a given file // Collect all toplevel names exported from a given file
let exports = Rc::new(Cache::new({ let exports = Rc::new(Cache::new({
let loaded = Rc::clone(&loaded); let loaded = loaded.clone();
let preparsed = Rc::clone(&preparsed); let preparsed = preparsed.clone();
move |path: Mrc<[String]>, _| -> ParseResult<Vec<String>, ELoad> { move |path: Mrc<[String]>, _| -> ParseResult<Vec<String>> {
let loaded = loaded.try_find(&path)?; let loaded = loaded.try_find(&path)?;
if let Loaded::Namespace(names) = loaded.as_ref() { if let Loaded::Namespace(names) = loaded.as_ref() {
return Ok(names.clone()); return Ok(names.clone());
@@ -91,9 +101,9 @@ where
})); }));
// Collect all toplevel names imported by a given file // Collect all toplevel names imported by a given file
let imports = Rc::new(Cache::new({ let imports = Rc::new(Cache::new({
let preparsed = Rc::clone(&preparsed); let preparsed = preparsed.clone();
let exports = Rc::clone(&exports); let exports = exports.clone();
move |path: Mrc<[String]>, _| -> ParseResult<HashMap<String, Mrc<[String]>>, ELoad> { move |path: Mrc<[String]>, _| -> ParseResult<HashMap<String, Mrc<[String]>>> {
let entv = preparsed.try_find(&path)?; let entv = preparsed.try_find(&path)?;
let import_entries = parse::imports(entv.iter()); let import_entries = parse::imports(entv.iter());
let mut imported_symbols: HashMap<String, Mrc<[String]>> = HashMap::new(); let mut imported_symbols: HashMap<String, Mrc<[String]>> = HashMap::new();
@@ -102,54 +112,55 @@ where
if let Some(ref name) = imp.name { if let Some(ref name) = imp.name {
if export.contains(name) { if export.contains(name) {
imported_symbols.insert(name.clone(), Mrc::clone(&imp.path)); imported_symbols.insert(name.clone(), Mrc::clone(&imp.path));
} } else {panic!("{:?} doesn't export {}", imp.path, name)}
} else { } else {
for exp in export.as_ref() { for exp in export.as_ref() {
imported_symbols.insert(exp.clone(), Mrc::clone(&imp.path)); imported_symbols.insert(exp.clone(), Mrc::clone(&imp.path));
} }
} }
} }
println!("Imports for {:?} are {:?}", path.as_ref(), imported_symbols);
Ok(imported_symbols) Ok(imported_symbols)
} }
})); }));
// Final parse, operators are correctly separated // Final parse, operators are correctly separated
let parsed = Rc::new(Cache::new({ let parsed = Rc::new(Cache::new({
let preparsed = Rc::clone(&preparsed); let preparsed = preparsed.clone();
let imports = Rc::clone(&imports); let imports = imports.clone();
let loaded = Rc::clone(&loaded); let loaded = loaded.clone();
move |path: Mrc<[String]>, _| -> ParseResult<Vec<FileEntry>, ELoad> { move |path: Mrc<[String]>, _| -> ParseResult<Vec<FileEntry>> {
let imported_ops: Vec<String> = let imported_ops: Vec<String> =
imports.try_find(&path)? imports.try_find(&path)?
.keys() .keys()
.chain(prelude.iter())
.filter(|s| parse::is_op(s)) .filter(|s| parse::is_op(s))
.cloned() .cloned()
.collect(); .collect();
// let parser = file_parser(&prelude, &imported_ops); // let parser = file_parser(&prelude, &imported_ops);
let pre = preparsed.try_find(&path)?; let pre = preparsed.try_find(&path)?;
if let Loaded::Module(source) = loaded.try_find(&path)?.as_ref() { match loaded.try_find(&path)?.as_ref() {
Ok(parse::reparse(&imported_ops, source.as_str(), &pre)?) Loaded::Module(source) => Ok(parse::reparse(&imported_ops, source.as_str(), &pre)?),
} else { Err(ModuleError::None) } Loaded::External(ast) => Ok(ast.clone()),
Loaded::Namespace(_) => Err(ModuleError::None)
}
} }
})); }));
let name_resolver_rc = RefCell::new(NameResolver::new({ let name_resolver_rc = RefCell::new(NameResolver::new({
let modname = Rc::clone(&modname); let modname = modname.clone();
move |path| { move |path| {
Some(modname.try_find(&path).ok()?.as_ref().clone()) Some(modname.try_find(&path).ok()?.as_ref().clone())
} }
}, { }, {
let imports = Rc::clone(&imports); let imports = imports.clone();
move |path| { move |path| {
imports.try_find(&path).map(|f| f.as_ref().clone()) imports.try_find(&path).map(|f| f.as_ref().clone())
} }
})); }));
// Turn parsed files into a bag of rules and a list of toplevel export names // Turn parsed files into a bag of rules and a list of toplevel export names
let resolved = Rc::new(Cache::new({ let resolved = Rc::new(Cache::new({
let parsed = Rc::clone(&parsed); let parsed = parsed.clone();
let exports = Rc::clone(&exports); let exports = exports.clone();
let imports = Rc::clone(&imports); let imports = imports.clone();
let modname = Rc::clone(&modname); move |path: Mrc<[String]>, _| -> ParseResult<Module> {
move |path: Mrc<[String]>, _| -> ParseResult<Module, ELoad> {
let mut name_resolver = name_resolver_rc.borrow_mut(); let mut name_resolver = name_resolver_rc.borrow_mut();
let module = Module { let module = Module {
rules: parsed.try_find(&path)? rules: parsed.try_find(&path)?
@@ -168,30 +179,26 @@ where
}) })
} else { None } } else { None }
}) })
.map(|rule| Ok(super::Rule { .map(|Rule{ source, target, prio }| Ok(super::Rule {
source: to_mrc_slice(rule.source.iter() source: to_mrc_slice(source.iter()
.map(|ex| name_resolver.process_expression(ex)) .map(|ex| name_resolver.process_expression(ex))
.collect::<Result<Vec<_>, _>>()?), .collect::<Result<Vec<_>, _>>()?),
target: to_mrc_slice(rule.target.iter() target: to_mrc_slice(target.iter()
.map(|ex| name_resolver.process_expression(ex)) .map(|ex| name_resolver.process_expression(ex))
.collect::<Result<Vec<_>, _>>()?), .collect::<Result<Vec<_>, _>>()?),
..rule prio
})) }))
.collect::<ParseResult<Vec<super::Rule>, ELoad>>()?, .collect::<ParseResult<Vec<super::Rule>>>()?,
exports: exports.try_find(&path)?.as_ref().clone(), exports: exports.try_find(&path)?.as_ref().clone(),
references: imports.try_find(&path)? references: imports.try_find(&path)?
.values() .values().cloned().collect()
.filter_map(|imps| {
modname.try_find(imps).ok().map(|r| r.as_ref().clone())
})
.collect()
}; };
Ok(module) Ok(module)
} }
})); }));
Cache::new({ Cache::new({
let resolved = Rc::clone(&resolved); let resolved = resolved.clone();
move |path: Mrc<[String]>, _| -> ParseResult<Vec<super::Rule>, ELoad> { move |path: Mrc<[String]>, _| -> ParseResult<Vec<super::Rule>> {
// Breadth-first search // Breadth-first search
let mut processed: HashSet<Mrc<[String]>> = HashSet::new(); let mut processed: HashSet<Mrc<[String]>> = HashSet::new();
let mut rules: Vec<super::Rule> = Vec::new(); let mut rules: Vec<super::Rule> = Vec::new();
@@ -202,12 +209,12 @@ where
processed.insert(el.clone()); processed.insert(el.clone());
pending.extend( pending.extend(
resolved.references.iter() resolved.references.iter()
.filter(|&v| !processed.contains(v)) .filter(|&v| !processed.contains(v))
.cloned() .cloned()
); );
rules.extend( rules.extend(
resolved.rules.iter().cloned() resolved.rules.iter().cloned()
) );
}; };
Ok(rules) Ok(rules)
} }

View File

@@ -46,7 +46,7 @@ pub enum Clause {
}, },
/// A parenthesized expression, eg. `(print out "hello")`, `[1, 2, 3]`, `{Some(t) => t}` /// A parenthesized expression, eg. `(print out "hello")`, `[1, 2, 3]`, `{Some(t) => t}`
S(char, Mrc<[Expr]>), S(char, Mrc<[Expr]>),
/// An explicit expression associated with the leftmost, outermost [Clause::Auto], eg. `read @Int` /// An explicit expression associated with the leftmost, outermost [Clause::Auto], eg. `read @Uint`
Explicit(Mrc<Expr>), Explicit(Mrc<Expr>),
/// A function expression, eg. `\x. x + 1` /// A function expression, eg. `\x. x + 1`
Lambda(String, Mrc<[Expr]>, Mrc<[Expr]>), Lambda(String, Mrc<[Expr]>, Mrc<[Expr]>),

View File

@@ -4,6 +4,7 @@ use std::rc::Rc;
use crate::utils::Side; use crate::utils::Side;
use crate::foreign::{ExternError, Atom}; use crate::foreign::{ExternError, Atom};
use super::Literal;
use super::path_set::PathSet; use super::path_set::PathSet;
use super::primitive::Primitive; use super::primitive::Primitive;
@@ -41,6 +42,22 @@ impl Debug for Clause {
} }
} }
impl TryFrom<Clause> for Literal {
type Error = Clause;
fn try_from(value: Clause) -> Result<Self, Self::Error> {
if let Clause::P(Primitive::Literal(l)) = value {Ok(l)}
else {Err(value)}
}
}
impl<'a> TryFrom<&'a Clause> for &'a Literal {
type Error = ();
fn try_from(value: &'a Clause) -> Result<Self, Self::Error> {
if let Clause::P(Primitive::Literal(l)) = value {Ok(l)}
else {Err(())}
}
}
/// Problems in the process of execution /// Problems in the process of execution
#[derive(Clone)] #[derive(Clone)]
pub enum RuntimeError { pub enum RuntimeError {
@@ -114,6 +131,10 @@ fn substitute(PathSet { steps, next }: &PathSet, value: &Clause, body: &Clause)
fn apply(f: &Clause, x: Rc<Clause>, id: usize) -> Result<Clause, InternalError> { fn apply(f: &Clause, x: Rc<Clause>, id: usize) -> Result<Clause, InternalError> {
match f { match f {
Clause::P(Primitive::Atom(Atom(a))) => Ok(Clause::Apply { // Don't execute a pre-application
f: Rc::new(a.run_once()?), // take a step in expanding the atom instead
x, id
}),
Clause::P(Primitive::ExternFn(f)) => f.apply(x.as_ref().clone()) Clause::P(Primitive::ExternFn(f)) => f.apply(x.as_ref().clone())
.map_err(|e| InternalError::Runtime(RuntimeError::Extern(e))), .map_err(|e| InternalError::Runtime(RuntimeError::Extern(e))),
fex@Clause::Apply{..} => Ok(Clause::Apply{ // Don't execute the pre-function expression fex@Clause::Apply{..} => Ok(Clause::Apply{ // Don't execute the pre-function expression

View File

@@ -5,7 +5,7 @@ use std::fmt::Debug;
#[derive(Clone, PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub enum Literal { pub enum Literal {
Num(NotNan<f64>), Num(NotNan<f64>),
Int(u64), Uint(u64),
Char(char), Char(char),
Str(String), Str(String),
} }
@@ -14,7 +14,7 @@ impl Debug for Literal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::Num(arg0) => write!(f, "{:?}", arg0), Self::Num(arg0) => write!(f, "{:?}", arg0),
Self::Int(arg0) => write!(f, "{:?}", arg0), Self::Uint(arg0) => write!(f, "{:?}", arg0),
Self::Char(arg0) => write!(f, "{:?}", arg0), Self::Char(arg0) => write!(f, "{:?}", arg0),
Self::Str(arg0) => write!(f, "{:?}", arg0), Self::Str(arg0) => write!(f, "{:?}", arg0),
} }

View File

@@ -7,6 +7,7 @@ pub(crate) mod interpreted;
mod postmacro; mod postmacro;
mod primitive; mod primitive;
mod path_set; mod path_set;
pub use path_set::PathSet;
pub use primitive::Primitive; pub use primitive::Primitive;
pub mod postmacro_to_interpreted; pub mod postmacro_to_interpreted;
pub use literal::Literal; pub use literal::Literal;

View File

@@ -100,7 +100,7 @@ fn write_expr_rec(state: &State, Expr(tpl_clause, tpl_typ): &Expr) -> Box<dyn It
write_slice_rec(state, body) write_slice_rec(state, body)
), out_typ.to_owned())), ), out_typ.to_owned())),
Clause::Lambda(name, typ, body) => box_once(Expr(Clause::Lambda( Clause::Lambda(name, typ, body) => box_once(Expr(Clause::Lambda(
if let Some(state_key) = name.strip_prefix('$') { if let Some(state_key) = name.strip_prefix("$_") {
if let Entry::Name(name) = &state[state_key] { if let Entry::Name(name) = &state[state_key] {
name.as_ref().to_owned() name.as_ref().to_owned()
} else {panic!("Lambda template name may only be derived from Lambda name")} } else {panic!("Lambda template name may only be derived from Lambda name")}

27
src/utils/interner.rs Normal file
View File

@@ -0,0 +1,27 @@
// use std::{collections::HashSet, hash::Hash};
// use hashbrown::HashMap;
// #[derive(Copy, Clone)]
// pub struct Interned<'a, T> {
// interner: &'a Interner<T>,
// data: &'a T,
// }
// impl<'a, T: Eq> Eq for Interned<'a, T> {}
// impl<'a, T: PartialEq> PartialEq for Interned<'a, T> {
// fn eq(&self, other: &Self) -> bool {
// if (self.interner as *const _) == (other.interner as *const _) {
// (self.data as *const _) == (other.data as *const _)
// } else {self.data == other.data}
// }
// }
// pub struct Interner<T> {
// data: HashSet<T>,
// hash_cache: HashMap<>
// }
// impl Interner<T> {
// }

View File

@@ -1,6 +1,7 @@
mod cache; mod cache;
pub mod translate; pub mod translate;
mod replace_first; mod replace_first;
mod interner;
// mod visitor; // mod visitor;
pub use replace_first::replace_first; pub use replace_first::replace_first;
pub use cache::Cache; pub use cache::Cache;