WIP lambda

* typesystem extended
* docstrings and callback
* tests defined, not implemented
This commit is contained in:
Ava Apples Affine 2023-03-12 20:29:39 -07:00
parent 7befdc869b
commit b0bd369c1d
Signed by: affine
GPG key ID: 3A4645B8CF806069
4 changed files with 88 additions and 24 deletions

View file

@ -45,7 +45,7 @@ top-level -> element1 -> "element2" -> 3 -> [] -> [] ->
Each node in memory has type information and potentially a cooresponding entry in a global symbol table. Each node in memory has type information and potentially a cooresponding entry in a global symbol table.
**** data types **** Data types
Relish leverages the following data types: Relish leverages the following data types:
- Strings: delimited by ~'~, ~"~, or ~`~ - Strings: delimited by ~'~, ~"~, or ~`~
- Integers: up to 128 bit signed integers - Integers: up to 128 bit signed integers
@ -53,10 +53,10 @@ Relish leverages the following data types:
- Booleans: ~true~ or ~false~ - Booleans: ~true~ or ~false~
- Symbols: an un-delimited chunk of text containing alphanumerics, ~-~, ~_~, or ~?~ - Symbols: an un-delimited chunk of text containing alphanumerics, ~-~, ~_~, or ~?~
Symbols and Functions are untyped. there is no restriction on what can be set/passed to what..... Symbols and Functions can contain data of any type. there is no restriction on what can be set/passed to what.....
However, internally Relish is statically typed, and many builtin functions will get very picky about what types are passed to them. However, internally Relish is typed, and many builtin functions will get very picky about what types are passed to them.
**** calling a function **** Calling a function
S-Expressions can represent function calls in addition to trees of data. A function call is a list of data starting with a symbol that is defined to be a function: S-Expressions can represent function calls in addition to trees of data. A function call is a list of data starting with a symbol that is defined to be a function:
#+BEGIN_SRC lisp #+BEGIN_SRC lisp
(dothing arg1 arg2 arg3) (dothing arg1 arg2 arg3)
@ -70,7 +70,7 @@ Function calls are executed as soon as the tree is evaluated. See the following
In this example, ~(add 5 2)~ is evaluated first, its result is then passed to ~(add 3 ...)~. In infix form: ~3 + (5 + 2)~. In this example, ~(add 5 2)~ is evaluated first, its result is then passed to ~(add 3 ...)~. In infix form: ~3 + (5 + 2)~.
*** Control flow *** Control flow
**** if **** If
An *if form* is the most basic form of conditional evaluation offered by Relish. An *if form* is the most basic form of conditional evaluation offered by Relish.
It is a function that takes lazily evaluated arguments: a condition, a then clause, and an else clause. It is a function that takes lazily evaluated arguments: a condition, a then clause, and an else clause.
If the condition evaluates to true, the then clause is evaluated and the result returned. If the condition evaluates to true, the then clause is evaluated and the result returned.
@ -89,7 +89,7 @@ If the condition evaluates to neither true nor false (a non-boolean value) a typ
(turn-on-my-flag global-state)) (turn-on-my-flag global-state))
#+END_SRC #+END_SRC
**** while **** While
Another popular control flow structure is the *while loop*. Another popular control flow structure is the *while loop*.
This is implemented as a condition followed by one or more bodies that are lazily evaluated only if the condition is true. This is implemented as a condition followed by one or more bodies that are lazily evaluated only if the condition is true.
Like the *if form*, if the conditional returns a non-boolean value the *while loop* will return an error. Like the *if form*, if the conditional returns a non-boolean value the *while loop* will return an error.
@ -101,7 +101,7 @@ Like the *if form*, if the conditional returns a non-boolean value the *while lo
(toggle-my-flag global-state)) ;; this is also evaluated (toggle-my-flag global-state)) ;; this is also evaluated
#+END_SRC #+END_SRC
**** let **** Let
*Let* is one of the most powerful forms Relish offers. The first body in a call to let is a list of lists. *Let* is one of the most powerful forms Relish offers. The first body in a call to let is a list of lists.
Specifically, a list of variable declarations that lookf like this: ~(name value)~. Specifically, a list of variable declarations that lookf like this: ~(name value)~.
Each successive variable definition can build off of the last one, like this: ~((step1 "hello") (step2 (concat step1 " ")) (step3 (concat step2 "world")))~. Each successive variable definition can build off of the last one, like this: ~((step1 "hello") (step2 (concat step1 " ")) (step3 (concat step2 "world")))~.
@ -122,8 +122,8 @@ Here you can see the usefulness of being able to declare multiple variables in q
Each variable is in scope for the duration of the let statement and then dropped when the statement has concluded. Each variable is in scope for the duration of the let statement and then dropped when the statement has concluded.
Thus, it is little cost to break complex calculations down into reusable parts. Thus, it is little cost to break complex calculations down into reusable parts.
**** TODO circuit **** TODO Circuit
**** not quite control flow **** Not quite control flow
Several other functions use lazy evaluation of their arguments. The below list is non-exhaustive: Several other functions use lazy evaluation of their arguments. The below list is non-exhaustive:
- toggle - toggle
- inc - inc
@ -151,7 +151,8 @@ CURRENT VALUE AND/OR BODY:
<builtin> <builtin>
#+END_SRC #+END_SRC
*** TODO quote and eval *** TODO Quote and Eval
*** TODO Lambda
*** TODO Defining variables and functions *** TODO Defining variables and functions
**** TODO Anatomy **** TODO Anatomy
**** TODO Naming conventions **** TODO Naming conventions
@ -321,22 +322,22 @@ This contains any executable target of this project. Notably the main shell file
Note: this section will not show the status of each item unless you are viewing it with a proper orgmode viewer. Note: this section will not show the status of each item unless you are viewing it with a proper orgmode viewer.
Note: this section only tracks the state of incomplete TODO items. Having everything on here would be cluttered. Note: this section only tracks the state of incomplete TODO items. Having everything on here would be cluttered.
*** TODO list contains via circuit *** TODO Lambda
*** TODO Lambda hack IMPLEMENTATION:
BAD IDEAS - [-] implement a new Ctr type to encapsulate function and args
- lambda function can use illegal name parameter for symbol names, guaranteeing non collision in symbol table - [ ] need a new case in store for when bound to a var
- add some incrementing variable to avoid lambdas colliding with lambdas (honestly store should be rewritten)
- custom drop implementation for Ctr that only overrides Symbol, checks for illegal name conventions, and yeets from symtable - [ ] need a case in eval that mirrors the function call case
(how do we get the sym table into the drop impl)
- when repl loops, clear lambdas out of the symtable
(shit hack and will only work for very simple cases. highly not ideal)
GOOD IDEA
- could implement a new Ctr type to encapsulate function and args
- would need a new case in store for when bound to a var but honestly store should be rewritten
- would need a case in eval that mirrors the function call case
DOCUMENTATION: DOCUMENTATION:
- let case for creating and applying a lambda - [ ] let case for creating and applying a lambda
TEST:
- [ ] lambda to_string input equivalency
- [ ] (eq? ((lambda (x y) (add x y)) 1 2) (add 1 2))
- [ ] let case for creating and applying a lambda
*** TODO list contains via circuit
*** TODO Map function *** TODO Map function
- DOCUMENTATION + TEST: - DOCUMENTATION + TEST:
apply a lambda to a list apply a lambda to a list
@ -352,6 +353,7 @@ Optionally return a list of new variables and/or functions?
Will need a concatenate function for tables Will need a concatenate function for tables
*** TODO Main shell calls Load function on arg and exits *** TODO Main shell calls Load function on arg and exits
*** TODO Ship a relish-based stdlib *** TODO Ship a relish-based stdlib
*** TODO Map library written in relish ("slowmap")
*** TODO FINISH DOCUMENTATION *** TODO FINISH DOCUMENTATION
*** TODO Shell module- *** TODO Shell module-
**** TODO only loadable via POSIX config var **** TODO only loadable via POSIX config var
@ -368,6 +370,7 @@ Overload Load function to call a binary too
*** TODO Create a dedicated community channel on matrix.sunnypup.io *** TODO Create a dedicated community channel on matrix.sunnypup.io
*** TODO Post to relevant channels *** TODO Post to relevant channels
*** TODO Custom ast pretty print *** TODO Custom ast pretty print
*** TODO Implement Compose for lambdas
*** TODO file operations *** TODO file operations
**** TODO read-to-string **** TODO read-to-string
**** TODO write-to-file **** TODO write-to-file

View file

@ -14,6 +14,7 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
use crate::sym::UserFn;
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::ops::{Add, Div, Index, Mul, Sub}; use std::ops::{Add, Div, Index, Mul, Sub};
@ -27,6 +28,7 @@ pub enum Ctr {
Float(f64), Float(f64),
Bool(bool), Bool(bool),
Seg(Seg), Seg(Seg),
Lambda(UserFn),
#[default] #[default]
None, None,
} }
@ -40,6 +42,7 @@ pub enum Type {
Float, Float,
Bool, Bool,
Seg, Seg,
Lambda,
None, None,
} }
@ -80,6 +83,7 @@ impl Ctr {
Ctr::Float(_s) => Type::Float, Ctr::Float(_s) => Type::Float,
Ctr::Bool(_s) => Type::Bool, Ctr::Bool(_s) => Type::Bool,
Ctr::Seg(_s) => Type::Seg, Ctr::Seg(_s) => Type::Seg,
Ctr::Lambda(_s) => Type::Lambda,
Ctr::None => Type::None, Ctr::None => Type::None,
} }
} }
@ -240,6 +244,7 @@ impl Clone for Ctr {
Ctr::Float(s) => Ctr::Float(*s), Ctr::Float(s) => Ctr::Float(*s),
Ctr::Bool(s) => Ctr::Bool(*s), Ctr::Bool(s) => Ctr::Bool(*s),
Ctr::Seg(s) => Ctr::Seg(s.clone()), Ctr::Seg(s) => Ctr::Seg(s.clone()),
Ctr::Lambda(s) => Ctr::Seg(s.clone()),
Ctr::None => Ctr::None, Ctr::None => Ctr::None,
} }
} }
@ -260,6 +265,7 @@ impl fmt::Display for Ctr {
} }
} }
Ctr::Seg(s) => write!(f, "{}", s), Ctr::Seg(s) => write!(f, "{}", s),
Ctr::Lambda(l) => write!(f, "{}", l),
Ctr::None => Ok(()), Ctr::None => Ok(()),
} }
} }
@ -398,6 +404,7 @@ impl fmt::Display for Type {
Type::Float => "float", Type::Float => "float",
Type::Bool => "bool", Type::Bool => "bool",
Type::Seg => "segment", Type::Seg => "segment",
Type::Lambda => "lambda",
Type::None => "none", Type::None => "none",
}; };
@ -414,6 +421,7 @@ impl std::convert::From<String> for Type {
"float" => Type::Float, "float" => Type::Float,
"bool" => Type::Bool, "bool" => Type::Bool,
"segment" => Type::Seg, "segment" => Type::Seg,
"lambda" => Type::Lambda,
_ => Type::None, _ => Type::None,
} }
} }

View file

@ -244,3 +244,44 @@ pub fn env_callback(_ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
} }
Ok(Ctr::None) Ok(Ctr::None)
} }
pub const LAMBDA_DOCSTRING: &str = "Takes two arguments of any type.
No args are evaluated when lambda is called.
Lambda makes sure the first argument is a list of symbols (or 'arguments') to the lambda function.
The next arg is stored in a tree to evaluate on demand.
Example: (lambda (x y) (add x y))
This can then be evaluated like so:
((lambda (x y) (add x y)) 1 2)
which is functionally equivalent to:
(add 1 2)";
pub fn lambda_callback(
ast: &Seg,
_syms: &mut SymTable
) -> Result<Ctr, String> {
let mut args = vec![];
if let Ctr::Seg(ref arg_head) = *ast.car {
if !arg_head.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Symbol(ref s) = *arg {
args.push(s.clone());
true
} else {
false
}
}) {
Err("all elements of first argumnets must be symbols".to_string())
} else {
if let Ctr::Seg(ref eval_head) = *ast.cdr {
Ok(Ctr::Lambda(UserFn{
ast: Box::new(eval_head.clone()),
arg_syms: args,
}))
} else {
Err("not enough args".to_string())
}
}
} else {
Err("first argument should be a list of symbols".to_string())
}
}

View file

@ -227,6 +227,18 @@ impl fmt::Display for Args {
} }
} }
impl fmt::Display for UserFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(lambda: (");
for i in self.arg_syms {
write!(f, "{} ", i);
}
write!(f, ") {})", self.ast);
Ok(())
}
}
impl fmt::Display for ValueType { impl fmt::Display for ValueType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {