From 34077344cac175269d64e713867e5c8205af68fc Mon Sep 17 00:00:00 2001 From: minneelyyyy Date: Wed, 16 Oct 2024 01:14:26 -0400 Subject: [PATCH] add function parameters and type declarations --- src/executor.rs | 93 +++++++++++++++-------- src/lib.rs | 67 ++++++++++++---- src/parser.rs | 194 ++++++++++++++++++++++++++++++++++++++--------- src/tokenizer.rs | 60 ++++++++++++++- 4 files changed, 333 insertions(+), 81 deletions(-) diff --git a/src/executor.rs b/src/executor.rs index 2133139..2ff5b1b 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -1,4 +1,4 @@ -use super::{Value, Type, FunctionDeclaration}; +use super::{Value, Type, Function, FunctionType}; use super::parser::{ParseTree, ParseError}; use std::collections::HashMap; @@ -17,13 +17,14 @@ pub enum RuntimeError { FunctionUndefined(String), NotAVariable(String), ParseFail(String, Type), + TypeError(Type, Type), IO(io::Error), } impl Display for RuntimeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::ParseError(e) => write!(f, "{e}"), + Self::ParseError(e) => write!(f, "Parser Error: {e}"), Self::NoOverloadForTypes(op, values) => write!(f, "No overload of `{op}` exists for the operands `[{}]`", values.iter().map(|x| format!("{}({x})", x.get_type())).collect::>().join(", ")), @@ -34,6 +35,7 @@ impl Display for RuntimeError { Self::NotAVariable(ident) => write!(f, "`{ident}` is a function but was attempted to be used like a variable"), Self::ParseFail(s, t) => write!(f, "`\"{s}\"` couldn't be parsed into {}", t), Self::IO(e) => write!(f, "{e}"), + Self::TypeError(left, right) => write!(f, "expected type `{left}` but got type `{right}`"), } } } @@ -50,12 +52,6 @@ enum Evaluation { Uncomputed(Box), } -#[derive(Clone, Debug)] -struct Function { - decl: FunctionDeclaration, - body: Option>, -} - #[derive(Clone, Debug)] enum Object { Variable(Evaluation), @@ -116,8 +112,8 @@ where (Value::Int(x), Value::Float(y)) => Ok(Value::Float(x as f64 + y)), (Value::Float(x), Value::Float(y)) => Ok(Value::Float(x + y)), (Value::String(x), Value::String(y)) => Ok(Value::String(format!("{x}{y}"))), - (Value::Array(x), y) => Ok(Value::Array([x, vec![y]].concat())), - (x, Value::Array(y)) => Ok(Value::Array([vec![x], y].concat())), + (Value::Array(_, x), y) => Ok(Value::Array(Type::Any, [x, vec![y]].concat())), + (x, Value::Array(_, y)) => Ok(Value::Array(Type::Any, [vec![x], y].concat())), (x, y) => Err(RuntimeError::NoOverloadForTypes("+".into(), vec![x, y])) }, ParseTree::Sub(x, y) => match (self.exec(x, locals)?, self.exec(y, locals)?) { @@ -235,19 +231,29 @@ where self.exec(scope, &mut Cow::Borrowed(&locals)) } }, - ParseTree::FunctionDefinition(ident, args, body, scope) => { - let existing = locals.get(&ident).or(self.globals.get(&ident)).cloned(); - + ParseTree::FunctionDefinition(func, scope) => { + let ident = func.name.clone().unwrap(); + + let existing = locals.get(&ident) + .or(self.globals.get(&ident)); + match existing { - Some(_) => Err(RuntimeError::ImmutableError(ident.clone())), + Some(Object::Function(f)) => { + if f.body.is_some() || f.arg_names.is_some() { + return Err(RuntimeError::ImmutableError(ident.clone())); + } + + let new_func = Function::named(func.name.unwrap().as_str(), func.t.clone(), func.arg_names.clone(), func.body.clone()); + + let locals = locals.to_mut(); + locals.insert(ident.clone(), Object::Function(new_func)); + + self.exec(scope, &mut Cow::Borrowed(&locals)) + } + Some(Object::Variable(_)) => Err(RuntimeError::ImmutableError(ident.clone())), None => { let locals = locals.to_mut(); - - locals.insert(ident.clone(), Object::Function(Function { - decl: FunctionDeclaration { _name: ident.clone(), args }, - body: Some(body) - })); - + locals.insert(ident.clone(), Object::Function(func)); self.exec(scope, &mut Cow::Borrowed(&locals)) } } @@ -262,8 +268,9 @@ where Value::Int(i) => i != 0, Value::Bool(b) => b, Value::String(s) => !s.is_empty(), - Value::Array(vec) => !vec.is_empty(), + Value::Array(_, vec) => !vec.is_empty(), Value::Nil => false, + x => return Err(RuntimeError::NoOverloadForTypes("?".into(), vec![x])), } { self.exec(body, locals) } else { @@ -274,8 +281,9 @@ where Value::Int(i) => i != 0, Value::Bool(b) => b, Value::String(s) => !s.is_empty(), - Value::Array(vec) => !vec.is_empty(), + Value::Array(_, vec) => !vec.is_empty(), Value::Nil => false, + x => return Err(RuntimeError::NoOverloadForTypes("?".into(), vec![x])), } { self.exec(istrue, locals) } else { @@ -284,17 +292,26 @@ where ParseTree::FunctionCall(ident, args) => { let obj = locals.get(&ident).or(self.globals.get(&ident)).cloned(); - if let Some(Object::Function(f)) = obj { - let locals = locals.to_mut(); - let body = f.body.ok_or(RuntimeError::FunctionUndefined(ident.clone()))?; + match obj { + Some(Object::Function(f)) => { + let locals = locals.to_mut(); - for (name, tree) in std::iter::zip(f.decl.args, args) { - locals.insert(name.clone(), Object::Variable(Evaluation::Computed(self.exec(Box::new(tree), &mut Cow::Borrowed(locals))?))); + for ((t, name), tree) in std::iter::zip(std::iter::zip(f.t.1, f.arg_names.unwrap()), args) { + let v = self.exec(Box::new(tree), &mut Cow::Borrowed(locals))?; + + if v.get_type() != t && t != Type::Any { + return Err(RuntimeError::TypeError(t, v.get_type())); + } + + locals.insert(name.clone(), match v { + Value::Function(func) => Object::Function(func), + _ => Object::Variable(Evaluation::Computed(v)) + }); + } + + self.exec(f.body.unwrap(), &mut Cow::Borrowed(&locals)) } - - self.exec(body, &mut Cow::Borrowed(&locals)) - } else { - Err(RuntimeError::FunctionUndeclared(ident.clone())) + _ => Err(RuntimeError::FunctionUndefined(ident.clone())) } }, ParseTree::Variable(ident) => { @@ -342,7 +359,7 @@ where Value::Float(x) => Ok(Value::Bool(x != 0.0)), Value::Bool(x) => Ok(Value::Bool(x)), Value::String(x) => Ok(Value::Bool(!x.is_empty())), - Value::Array(vec) => Ok(Value::Bool(!vec.is_empty())), + Value::Array(_, vec) => Ok(Value::Bool(!vec.is_empty())), x => Err(RuntimeError::NoOverloadForTypes("bool".into(), vec![x])), }, ParseTree::StringCast(x) => Ok(Value::String(format!("{}", self.exec(x, locals)?))), @@ -352,6 +369,18 @@ where Ok(Value::Nil) } } + ParseTree::FunctionDeclaration(func, scope) => { + let locals = locals.to_mut(); + let name = func.name.clone().unwrap(); + + if locals.contains_key(&name) { + Err(RuntimeError::ImmutableError(name.clone())) + } else { + locals.insert(name, Object::Function(func)); + self.exec(scope, &mut Cow::Borrowed(&locals)) + } + } + ParseTree::LambdaDefinition(func) => Ok(Value::Function(func)), } } } diff --git a/src/lib.rs b/src/lib.rs index 5ef7c0d..3020752 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +3,12 @@ mod parser; mod executor; use executor::{Executor, RuntimeError}; -use parser::Parser; +use parser::{ParseTree, Parser}; use tokenizer::Tokenizer; use std::fmt::Display; use std::io::{Write, Read, BufRead}; +use std::fmt; #[derive(Clone, Debug, PartialEq)] pub enum Type { @@ -15,21 +16,21 @@ pub enum Type { Int, Bool, String, - Array, - _Function(Box, Vec), + Array(Box), + Function(FunctionType), Nil, Any, } impl Display for Type { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", match self { Self::Float => "Float".into(), Self::Int => "Int".into(), Self::Bool => "Bool".into(), Self::String => "String".into(), - Self::Array => format!("Array"), - Self::_Function(r, _) => format!("Function -> {}", *r), + Self::Array(t) => format!("[{t}]"), + Self::Function(r) => format!("{r}"), Self::Nil => "Nil".into(), Self::Any => "Any".into(), }) @@ -43,7 +44,8 @@ pub enum Value { Int(i64), Bool(bool), String(String), - Array(Vec), + Array(Type, Vec), + Function(Function), Nil, } @@ -54,8 +56,9 @@ impl Value { Self::Int(_) => Type::Int, Self::Bool(_) => Type::Bool, Self::String(_) => Type::String, - Self::Array(_) => Type::Array, + Self::Array(t, _) => Type::Array(Box::new(t.clone())), Self::Nil => Type::Nil, + Self::Function(f) => Type::Function(f.t.clone()), } } } @@ -67,16 +70,54 @@ impl Display for Value { Self::Int(x) => write!(f, "{x}"), Self::Bool(x) => write!(f, "{}", if *x { "true" } else { "false" }), Self::String(x) => write!(f, "\"{x}\""), - Self::Array(v) => write!(f, "[{}]", v.iter().map(|x| format!("{x}")).collect::>().join(" ")), + Self::Array(_t, v) => write!(f, "[{}]", v.iter().map(|x| format!("{x}")).collect::>().join(" ")), + Self::Function(func) => { + if let Some(name) = &func.name { + write!(f, "Function({}, {}, {})", name, func.t.0, func.t.1.iter().map(|x| format!("{x}")).collect::>().join(", ")) + } else { + write!(f, "{}", func.t) + } + } Self::Nil => write!(f, "nil"), } } } -#[derive(Clone, Debug)] -pub(crate) struct FunctionDeclaration { - _name: String, - args: Vec, +#[derive(Clone, Debug, PartialEq)] +pub struct FunctionType(Box, Vec); + +impl Display for FunctionType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Function({}, {})", self.0, self.1.iter().map(|x| format!("{x}")).collect::>().join(", ")) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Function { + name: Option, + t: FunctionType, + arg_names: Option>, + body: Option>, +} + +impl Function { + pub fn lambda(t: FunctionType, arg_names: Vec, body: Option>) -> Self { + Self { + name: None, + t, + arg_names: Some(arg_names), + body + } + } + + pub fn named(name: &str, t: FunctionType, arg_names: Option>, body: Option>) -> Self { + Self { + name: Some(name.to_string()), + t, + arg_names, + body + } + } } pub struct Runtime<'a, R: BufRead> { diff --git a/src/parser.rs b/src/parser.rs index 3f7cfc4..f61d288 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,4 +1,4 @@ -use super::{Type, Value, FunctionDeclaration}; +use super::{Value, Type, Function, FunctionType}; use super::tokenizer::{Token, TokenizeError, Op}; use std::error; @@ -15,7 +15,9 @@ pub enum ParseError { FunctionUndefined(String), VariableUndefined(String), UnmatchedArrayClose, + UnwantedToken(Token), TokenizeError(TokenizeError), + ImmutableError(String), } impl Display for ParseError { @@ -28,14 +30,16 @@ impl Display for ParseError { ParseError::VariableUndefined(name) => write!(f, "Undefined variable `{name}`"), ParseError::NoInput => write!(f, "No input given"), ParseError::UnmatchedArrayClose => write!(f, "there was an unmatched array closing operator `]`"), - ParseError::TokenizeError(e) => write!(f, "{e}"), + ParseError::TokenizeError(e) => write!(f, "Tokenizer Error: {e}"), + ParseError::ImmutableError(i) => write!(f, "attempt to redeclare {i} met with force"), + ParseError::UnwantedToken(_t) => write!(f, "unexpected token"), } } } impl error::Error for ParseError {} -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub(crate) enum ParseTree { // Mathematical Operators Add(Box, Box), @@ -59,7 +63,9 @@ pub(crate) enum ParseTree { // Defining Objects Equ(String, Box, Box), LazyEqu(String, Box, Box), - FunctionDefinition(String, Vec, Box, Box), + FunctionDefinition(Function, Box), + FunctionDeclaration(Function, Box), + LambdaDefinition(Function), // Functional Operations Compose(Box, Box), @@ -111,8 +117,8 @@ macro_rules! three_arg { impl ParseTree { fn parse( tokens: &mut I, - globals: &HashMap, - locals: &mut Cow>) -> Result + globals: &HashMap, + locals: &mut Cow>) -> Result where I: Iterator>, { @@ -124,8 +130,8 @@ impl ParseTree { // If it is found to be a function, get its argument count. // During parsing, we only keep track of function definitions // so that we know how many arguments it takes - if let Some(decl) = locals.clone().get(&ident).or(globals.clone().get(&ident)) { - let args = decl.args.iter() + if let Some(f) = locals.clone().get(&ident).or(globals.clone().get(&ident)) { + let args = f.t.1.iter() .map(|_| ParseTree::parse(tokens, globals, locals)).collect::, ParseError>>()?; Ok(ParseTree::FunctionCall(ident.clone(), args)) @@ -162,35 +168,59 @@ impl ParseTree { Err(ParseError::InvalidIdentifier) } } - Op::FunctionDeclare(nargs) => { + Op::FunctionDefine(nargs) => { let token = tokens.next() .ok_or(ParseError::UnexpectedEndInput)? .map_err(|e| ParseError::TokenizeError(e))?; - if let Token::Identifier(ident) = token { - let args: Vec = tokens.take(nargs) - .map(|token| match token { - Ok(Token::Identifier(ident)) => Ok(ident), - Ok(_) => Err(ParseError::InvalidIdentifier), - Err(e) => Err(ParseError::TokenizeError(e)), - }) - .collect::, ParseError>>()?; + if let Token::Identifier(ident) = token { + let args: Vec = tokens.take(nargs) + .map(|token| match token { + Ok(Token::Identifier(ident)) => Ok(ident), + Ok(_) => Err(ParseError::InvalidIdentifier), + Err(e) => Err(ParseError::TokenizeError(e)), + }) + .collect::, ParseError>>()?; + + let f = if locals.contains_key(&ident) { + let locals = locals.to_mut(); + let f = locals.get(&ident).unwrap(); + let f = f.clone(); + + // iterate over f's types and push them + for (t, name) in std::iter::zip(f.t.1.clone(), args.clone()) { + match t { + Type::Function(finner) => { + locals.insert(name.clone(), Function::named(&name, finner, None, None)); + } + _ => (), + } + } + + Function::named( + &ident, + f.t.clone(), + Some(args), + Some(Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&locals))?))) + } else { + let f = Function::named( + &ident, + FunctionType(Box::new(Type::Any), args.iter().map(|_| Type::Any).collect()), + Some(args), + Some(Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&locals))?))); let locals = locals.to_mut(); - locals.insert(ident.clone(), FunctionDeclaration { - _name: ident.clone(), - args: args.clone(), - }); + locals.insert(ident.clone(), f.clone()); - Ok(ParseTree::FunctionDefinition( - ident, - args, - Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&*locals))?), - Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&*locals))?))) - } else { - Err(ParseError::InvalidIdentifier) - } + f + }; + + Ok(ParseTree::FunctionDefinition(f, + Box::new(ParseTree::parse(tokens, globals, locals)?))) + } else { + Err(ParseError::InvalidIdentifier) + } } Op::Compose => two_arg!(Compose, tokens, globals, locals), Op::Id => one_arg!(Id, tokens, globals, locals), @@ -232,25 +262,119 @@ impl ParseTree { .collect::>()?; let tree = trees.into_iter().fold( - ParseTree::Constant(Value::Array(vec![])), + ParseTree::Constant(Value::Array(Type::Any, vec![])), |acc, x| ParseTree::Add(Box::new(acc), Box::new(x.clone())), ); Ok(tree) } - Op::Empty => Ok(ParseTree::Constant(Value::Array(vec![]))), + Op::Empty => Ok(ParseTree::Constant(Value::Array(Type::Any, vec![]))), Op::CloseArray => Err(ParseError::UnmatchedArrayClose), Op::NotEqualTo => two_arg!(NotEqualTo, tokens, globals, locals), Op::And => two_arg!(And, tokens, globals, locals), Op::Or => two_arg!(Or, tokens, globals, locals), + Op::FunctionDeclare(arg_count) => { + let name = match tokens.next() + .ok_or(ParseError::UnexpectedEndInput)? + .map_err(|e| ParseError::TokenizeError(e))? + { + Token::Identifier(x) => x, + _ => return Err(ParseError::InvalidIdentifier), + }; + + let args: Vec = (0..arg_count) + .map(|_| Self::parse_type(tokens)) + .collect::>()?; + + let rett = Self::parse_type(tokens)?; + + if locals.contains_key(&name) { + println!("{name} already found: {locals:?}"); + return Err(ParseError::ImmutableError(name.clone())); + } + + let f = Function::named( + &name, + FunctionType(Box::new(rett), args), + None, + None); + + let locals = locals.to_mut(); + + locals.insert(name, f.clone()); + + Ok(ParseTree::FunctionDeclaration( + f, + Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&*locals))?))) + } + Op::LambdaDefine(arg_count) => { + let args: Vec = tokens.take(arg_count) + .map(|token| match token { + Ok(Token::Identifier(ident)) => Ok(ident), + Ok(_) => Err(ParseError::InvalidIdentifier), + Err(e) => Err(ParseError::TokenizeError(e)), + }) + .collect::, ParseError>>()?; + + Ok(ParseTree::LambdaDefinition( + Function::lambda( + FunctionType(Box::new(Type::Any), args.clone().into_iter().map(|_| Type::Any).collect()), + args, + Some(Box::new(ParseTree::parse(tokens, globals, &mut Cow::Borrowed(&*locals))?))))) + } + Op::NonCall => { + let ident = match tokens.next().ok_or(ParseError::UnexpectedEndInput)? + .map_err(|e| ParseError::TokenizeError(e))? + { + Token::Identifier(x) => x, + _ => return Err(ParseError::InvalidIdentifier), + }; + + if let Some(f) = locals.clone().get(&ident).or(globals.clone().get(&ident)).cloned() { + Ok(ParseTree::Constant(Value::Function(f))) + } else { + Err(ParseError::FunctionUndefined(ident.clone())) + } + } } } + t => Err(ParseError::UnwantedToken(t)), } }, Some(Err(e)) => Err(ParseError::TokenizeError(e)), None => Err(ParseError::NoInput), } } + + fn parse_type(tokens: &mut I) -> Result + where + I: Iterator>, + { + match tokens.next() { + Some(Ok(Token::Type(t))) => Ok(t), + Some(Ok(Token::Operator(Op::FunctionDefine(n)))) => { + let args: Vec = (0..n) + .map(|_| Self::parse_type(tokens)) + .collect::>()?; + + let rett = Self::parse_type(tokens)?; + + Ok(Type::Function(FunctionType(Box::new(rett), args.clone()))) + }, + Some(Ok(Token::Operator(Op::OpenArray))) => { + let t = Self::parse_type(tokens)?; + let _ = match tokens.next() { + Some(Ok(Token::Operator(Op::CloseArray))) => (), + _ => return Err(ParseError::UnmatchedArrayClose), + }; + + Ok(Type::Array(Box::new(t))) + } + Some(Ok(t)) => Err(ParseError::UnwantedToken(t.clone())), + Some(Err(e)) => Err(ParseError::TokenizeError(e)), + None => Err(ParseError::UnexpectedEndInput), + } + } } /// Parses input tokens and produces ParseTrees for an Executor @@ -260,8 +384,8 @@ pub(crate) struct Parser>> { // These are used to keep track of functions in the current context // by the parser. otherwise the parser would have no way to tell // if the program `* a b 12` is supposed to be ((* a b) (12)) or (* (a b) 12) - globals: HashMap, - locals: HashMap, + globals: HashMap, + locals: HashMap, } impl>> Parser { @@ -273,7 +397,7 @@ impl>> Parser { } } - pub fn globals(self, globals: HashMap) -> Self { + pub fn globals(self, globals: HashMap) -> Self { Self { tokens: self.tokens, globals, @@ -281,7 +405,7 @@ impl>> Parser { } } - pub fn locals(self, locals: HashMap) -> Self { + pub fn locals(self, locals: HashMap) -> Self { Self { tokens: self.tokens, globals: self.globals, diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 6e4ce48..69e1adb 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -2,6 +2,8 @@ use std::iter::Peekable; use std::{error, io}; use std::collections::{VecDeque, HashMap}; +use crate::Type; + use super::Value; use std::fmt::{Display, Formatter}; use std::io::{BufRead, Cursor}; @@ -49,7 +51,9 @@ pub(crate) enum Op { Equ, Mod, LazyEqu, + FunctionDefine(usize), FunctionDeclare(usize), + LambdaDefine(usize), Compose, Id, If, @@ -71,6 +75,7 @@ pub(crate) enum Op { Empty, And, Or, + NonCall, } #[derive(Debug, Clone)] @@ -78,6 +83,7 @@ pub(crate) enum Token { Identifier(String), Operator(Op), Constant(Value), + Type(Type), } fn get_dot_count>(s: &mut Peekable) -> Option { @@ -111,6 +117,13 @@ impl Token { "print" => Ok(Token::Operator(Op::Print)), "empty" => Ok(Token::Operator(Op::Empty)), + // Types + "Any" => Ok(Token::Type(Type::Any)), + "Int" => Ok(Token::Type(Type::Int)), + "Float" => Ok(Token::Type(Type::Float)), + "Bool" => Ok(Token::Type(Type::Bool)), + "String" => Ok(Token::Type(Type::String)), + // then identifiers and numbers _ => { if identifier.is_match(s) { @@ -156,7 +169,9 @@ impl Tokenizer { ("%", Op::Mod), ("=", Op::Equ), (".", Op::LazyEqu), - (":", Op::FunctionDeclare(1)), + (":", Op::FunctionDefine(1)), + ("?:", Op::FunctionDeclare(1)), + (";", Op::LambdaDefine(1)), ("~", Op::Compose), (",", Op::Id), ("?", Op::If), @@ -172,6 +187,7 @@ impl Tokenizer { ("!", Op::Not), ("&&", Op::And), ("||", Op::Or), + ("'", Op::NonCall), ]); let c = if let Some(c) = iter.next() { @@ -256,6 +272,26 @@ impl Tokenizer { }; Op::FunctionDeclare(n + count) } + Op::FunctionDefine(n) => { + let count = match get_dot_count(&mut iter) { + Some(count) => count, + None => { + self.tokens.push_back(Err(TokenizeError::InvalidDynamicOperator(token))); + return; + } + }; + Op::FunctionDefine(n + count) + } + Op::LambdaDefine(n) => { + let count = match get_dot_count(&mut iter) { + Some(count) => count, + None => { + self.tokens.push_back(Err(TokenizeError::InvalidDynamicOperator(token))); + return; + } + }; + Op::LambdaDefine(n + count) + } op => op.clone(), }))); @@ -326,4 +362,26 @@ impl std::iter::Iterator for Tokenizer { Err(e) => Some(Err(TokenizeError::IO(e))), } } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use crate::parser::{Parser, ParseTree, ParseError}; + + use super::*; + + #[test] + fn uwu() { + let program = "?:. apply : Any Any Any Any :. apply f x f x : id x x apply ; x id x 12"; + + let tokens: Vec = Tokenizer::from_str(program).unwrap().collect::>().unwrap(); + + println!("{tokens:?}"); + + let trees: Result, ParseError> = Parser::new(tokens.into_iter().map(|x| Ok(x))).collect(); + + println!("{trees:?}"); + } } \ No newline at end of file