Skip to content

Commit cbc97b7

Browse files
committed
Restructured project and split up into appropriate files
(commit larger than intended); "dyn Any" is now expressed with a large 'Value' enum, Expr type removedRestructured project and split up into appropriate files; "dyn Any" is now expressed with a large 'Value' enum, Expr type removed
1 parent 486ad7b commit cbc97b7

File tree

8 files changed

+284
-59
lines changed

8 files changed

+284
-59
lines changed

clojureRS.org

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
* Design Notes
2+
** Dynamic Typing
3+
Let's say we have a function like the invoke function (check out
4+
clojure.lang.IFn) for function-like types, which takes a variable
5+
number of arguments whose types are not known at compile time. In
6+
Java, this looks like
7+
8+
#+BEGIN_SRC java
9+
public Object invoke(Object arg1,Object arg2 ...)
10+
#+END_SRC
11+
12+
In Rust, for now to my knowledge our two options for dynamic typing
13+
are to
14+
15+
1. Use trait objects (combined with the Any trait specifically), something like
16+
#+BEGIN_SRC rust
17+
fn invoke<'a>(arg1: &'a dyn Any, ..) -> &'a dyn Any
18+
#+END_SRC
19+
20+
(But, considering that really we want to be often returning a
21+
new value with invoke, and thus our 'reference', despite
22+
starting in the function, should be existing beyond the function
23+
itself, we'd more so probably need to return a proper pointer on
24+
the heap, perhaps a Box<dyn Any>. When I was experimenting with
25+
this approach, I ended up using a Rc<dyn Any>, which fit better
26+
with some other implementation details and was 'easier', but there
27+
was still a lot to explore there)
28+
29+
or
30+
31+
2. To have a wrapper ADT (enum) that knows all possible types ahead of time; ie, something like
32+
#+BEGIN_SRC rust
33+
fn invoke(arg1: &Value,..) -> Value
34+
35+
enum Value {
36+
I32(i32),
37+
Symbol(Symbol),
38+
Keyword(Keyword),
39+
..
40+
}
41+
#+END_SRC
42+
43+
For now, I have moved forward with #2, as there appear to be some
44+
major issues one runs into with #1, although I am open to hearing
45+
from others wiser than I in Rust
46+
*** TODO Document problem with #1
47+
48+
** Exceptions
49+
How best represent exceptions?
50+
*** Conditions
51+
First off, I'd like to play with having flat out Conditions, as
52+
you have in something like Common Lisp, over Exceptions. This
53+
would be a difference from Clojure, so I'm not sure if this
54+
sort of divergence would require me not call this Clojure (at the
55+
same time, 'a full on Clojure that gets to live on its own, and be
56+
all that it wants to be without inheriting the restrictions of the
57+
JVM' is part of what I want to play with here).
58+
*** Implementation
59+
There's a few things to think about here, for now let's just have
60+
erroneous situations flat out return a Condition type, and start
61+
adding more behavior when we get back to this.
62+
63+
** Keeping the codebase close
64+
Originally, at least ,the goal was to keep the Rust base as similar
65+
to the Java / C# codebase as possible for consistency, but now I am
66+
thinking the program may just as easily end up split up and
67+
designed completely differently.
68+
69+
Either way, each part in common will try to be as consistent with
70+
the original Java version as possible -- and sometimes this will
71+
involve not going quite with Rust conventions, as is the example of
72+
the IFn trait, which for now is keeping the IFn name. See notes at
73+
top of IFn for more info
74+
75+
** Explore more the idea of a clean rust FFI / interop, in the usual spirit of Clojure
76+
I get the impression that runtime reflection in Rust would be difficult if not downright
77+
impossible, so for now I would like to look into producing our interop functions at compile time
78+
(or rather, 'pre compile time'; parsing our rust files, producing the appropriate ClojureRS rust code bridging
79+
the two, and the compiling the entire project after)
80+
81+
** Clojure map comments
82+
For now, I decided to add some meta data to some of the Rust-Clojure functions as an actual map in the comment
83+
(ie,
84+
#+BEGIN_SRC rust
85+
/*
86+
{:clojure-fn "+"}
87+
*/
88+
struct AddFn {
89+
}
90+
#+END_SRC
91+
I am not 100% I have a conscious reasoning yet, but I often like
92+
expressing data just as data, this is one of the draws of Clojure
93+
for me as it is. The map here is explicit, it is human readable, but
94+
also easily reflected read and manipulated by software. And it is
95+
expressed with the same, to-the-point language you can use to
96+
express any other information.
97+
98+
Anyways, for now I am only listing the Var this function should
99+
correspond to, as all other information can be found on said Var,
100+
although if I later realize it helps to have this information on
101+
the Rust implementation of a function at a glance , I'm not against
102+
adding it there, even if its repeat information

readme.org

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
* ClojureRS
2+
3+
Put simply, Clojure implemented atop Rust! My main goals here are
4+
5+
** Project Goals:
6+
1. To create a version of Clojure that gets to exist independent of a particular platform
7+
2. To explore a Clojure that, being more indepdent, has
8+
a. Proper tail call elimination
9+
b. Conditions / Restarts
10+
c. Any other feature it might not normally be able to indulge
11+
3. To, should the project progress, begin looking more serious into advanced compilation techniques,
12+
to create a serious Clojure implementation
13+
14+
Check clojureRS.org (the file, not website) for more notes about
15+
the design of the language, which should grow as the program
16+
grows.
17+
18+
** Personal Goals:
19+
1. Work a bit closer to the metal, and do so with Rust, two pleaures of mine
20+
2. Work more with compilation, another pleasure
21+
3. Give myself a functional lisp to script with -- but not
22+
necessarily because of any issue with our other clojure-scripting
23+
options

src/main.rs

Lines changed: 46 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,50 @@
1-
use std::any::{Any, TypeId};
2-
use std::collections::HashMap;
3-
use std::hash::{Hash,Hasher};
4-
use dyn_clone::DynClone;
5-
6-
#[derive(Hash,PartialEq,Eq,Copy,Clone)]
7-
struct Symbol {
8-
name: &'static str
9-
}
1+
mod rust_core;
2+
mod symbol;
3+
mod type_tag;
4+
mod value;
105

11-
trait IFn: DynClone {
12-
fn invoke(&self,args: &[&dyn Any]) -> Box<dyn Any>;
13-
}
14-
dyn_clone::clone_trait_object!(IFn);
6+
use rust_core::AddFn;
7+
use crate::value::{IFn,ToValue};
158

16-
#[derive(Clone)]
17-
struct AddFn {
18-
}
19-
impl IFn for AddFn {
20-
fn invoke(&self,args: &[&dyn Any]) -> Box<dyn Any>
21-
{
22-
Box::new(args.into_iter().fold(0,|a,b| {
23-
let _a = a as i64;
24-
if let Some(_b) = b.downcast_ref::<i64>() {
25-
_a + *_b
26-
}
27-
else {
28-
_a
29-
}
30-
31-
}))
32-
}
33-
}
34-
use crate::Expr::*;
35-
#[derive(Clone)]
36-
enum Expr {
37-
SymbolExpr(Symbol),
38-
FnExpr(Box<dyn IFn>),
39-
i32Expr(i32),
40-
nilExpr
41-
}
42-
impl Expr {
43-
fn eval(&self,environment: &HashMap<Symbol,Expr>) -> Expr{
44-
match self {
45-
SymbolExpr(sym) => match environment.get(&sym) {
46-
Some(expr) => expr.clone(),
47-
_ => nilExpr
48-
},
49-
_ => nilExpr
50-
}
51-
52-
}
53-
}
9+
fn main()
10+
{
11+
let add_fn = AddFn{};
12+
let result = add_fn.invoke(&[&5_i32.to_value(),&6_i32.to_value(),&10_i32.to_value()]);
5413

55-
fn main() {
56-
let add = AddFn{};
57-
let answer = add.invoke(&[&(1 as i64) as &dyn Any ,&(2 as i64) as &dyn Any]);
58-
let answer_as_num = answer.downcast_ref::<i64>();// as Box<i64>;
59-
60-
println!("Start:");
61-
println!("{:?}",answer_as_num);
62-
println!("Edn:");
14+
println!("{:?}",result);
6315
}
16+
17+
18+
19+
20+
21+
22+
23+
24+
25+
26+
27+
28+
29+
30+
31+
32+
33+
34+
35+
36+
37+
38+
39+
40+
41+
42+
43+
44+
45+
46+
47+
48+
49+
50+

src/namespace.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use std::collections::HashMap;
2+
use symbol::Symbol;
3+
use value::Value;
4+
5+
pub struct Namespace {
6+
name: Symbol,
7+
mappings: HashMap<Symbol,Value>
8+
}
9+
10+
struct Namespaces(HashMap<Symbol,Namespace>);

src/rust_core.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//use crate::ifn::IFn;
2+
use crate::value::Value;
3+
use crate::value::{IFn,ToValue};
4+
/*
5+
This module will hold the core functions and macros that Clojure will
6+
hook into; Functions / Macros like "+", "fn*", "let", "cond", etc
7+
8+
This is still experimental, and we may instead undo this all and
9+
represent fn* and let and the like the same it is done in ClojureJVM,
10+
where I believe they are defined flat out in the Compiler class
11+
12+
However, even in that case this may stick around to implement basic
13+
functions like "+" that usually rely on interop
14+
15+
*/
16+
17+
/*
18+
{:clojure-equivalent '+,
19+
:arglists '([name doc-string? attr-map? [params*] body]
20+
[name doc-string? attr-map? ([params*] body)+ attr-map?])
21+
*/
22+
#[derive(Debug)]
23+
pub struct AddFn {
24+
25+
26+
}
27+
impl IFn for AddFn {
28+
fn invoke(&self,args: &[&Value]) -> Value {
29+
args.into_iter().fold(0_i32.to_value(),|a,b| {
30+
match a {
31+
Value::I32(a_) => match b {
32+
Value::I32(b_) => Value::I32(a_ + b_),
33+
// @TODO insert actual value of b and type into error message
34+
_ => Value::Condition(String::from("Type mismatch; Expecting: (i32 | i64 | f32 | f64), Found: "))
35+
}
36+
// @TODO insert actual value of b and type into error message
37+
_ => Value::Condition(String::from("Type mismatch: Expecting: (i32 | i64 | f32 | f64), Found: "))
38+
39+
}
40+
})
41+
}
42+
}

src/symbol.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use std::hash::{Hash,Hasher};
2+
3+
#[derive(Hash,PartialEq,Eq,Clone,Debug)]
4+
pub struct Symbol {
5+
pub name: String
6+
}

src/type_tag.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub enum TypeTag {
2+
I32,
3+
Symbol,
4+
IFn,
5+
Condition
6+
}

src/value.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use crate::symbol::Symbol;
2+
use crate::type_tag::TypeTag;
3+
use std::collections::HashMap;
4+
use std::rc::Rc;
5+
use std::fmt::Debug;
6+
pub trait IFn : Debug {
7+
fn invoke(&self,args: &[&Value]) -> Value;
8+
}
9+
#[derive(Debug)]
10+
pub enum Value {
11+
I32(i32),
12+
Symbol(Symbol),
13+
IFn(Rc<dyn IFn>),
14+
Condition(String)
15+
}
16+
impl Value {
17+
fn type_tag(&self) -> TypeTag
18+
{
19+
match self {
20+
Value::I32(_) => TypeTag::I32,
21+
Value::Symbol(_) => TypeTag::Symbol,
22+
Value::IFn(_) => TypeTag::IFn,
23+
Value::Condition(_) => TypeTag::Condition
24+
}
25+
}
26+
fn eval(self : Rc<Value>, environment: &HashMap<Symbol,Rc<Value>>) -> Rc<Value> {
27+
match &*self {
28+
Value::Symbol(symbol) => match environment.get(symbol) {
29+
Some(val) => Rc::clone(val),
30+
_ => Rc::new(Value::Condition(format!("Undefined symbol {}",symbol.name)))
31+
}
32+
// I32, Fn
33+
_ => Rc::clone(&self),
34+
}
35+
}
36+
}
37+
38+
pub trait ToValue {
39+
fn to_value(&self) -> Value;
40+
}
41+
42+
impl ToValue for i32 {
43+
fn to_value(&self) -> Value {
44+
Value::I32(self.clone())
45+
}
46+
}
47+
48+
49+

0 commit comments

Comments
 (0)