diff --git a/README.md b/README.md index 371850b..4c0f704 100644 --- a/README.md +++ b/README.md @@ -301,22 +301,18 @@ The most basic pipeline operator is `|>`. `|>` will call the function on the rig 0..10 |? fn(x) (x % 3 != 0) |> list |> println; ``` -`|//` and `|\\` fold the iterator on the left over the function on the right. This is commonly used to get the sum, product, minimum, or maximum of an iterator. `|//` starts folding from the beginning of the iterator, `|\\` starts from the end. In many use cases (specifically, when the function used is associative), either can be used. `|//` should be preferred in these cases, as it is more efficient (`|\\` has to collect the entire iterator into a list first). +`|//` folds the iterator on the left over the function on the right. This is commonly used to get the sum, product, minimum, or maximum of an iterator. `|//` starts folding from the beginning of the iterator and goes to the end. ``` # == ((((0+1)+2)+3)+4) 0..5 |// fn(x,y) (x + y) |> println; -# == (0+(1+(2+(3+4)))) -0..5 |\\ fn(x,y) (x + y) |> println; ``` -In some cases (particulary when the function is not commutative) it may be desirable to use an initial value instead of taking the first two values from the list. This can be accomplished with the operators `|/` and `|\`. These operators are ternary, so the right-hand side must include the initial value and the function, separated by a comma. For `|/` this initial value will be used as the first argument during the first iteration, for `|\` it will be used as the second argument during the first iteration. +In some cases (particulary when the function is not commutative) it may be desirable to use an initial value instead of taking the first two values from the list. This can be accomplished with the operator `|/`. This operators are ternary, so the right-hand side must include the initial value and the function, separated by a comma. ``` # == (((((0+2)+3)+4)+5)+6) 2..7 |/ 0, fn(x,y) (x + y) |> println; -# == (2+(3+(4+(5+(6+0))))) -2..7 |\ 0, fn(x,y) (x + y) |> println; ``` ## Structs (WIP) diff --git a/complexpr-stdlib/src/io.rs b/complexpr-stdlib/src/io.rs index eaaaf63..8b66573 100644 --- a/complexpr-stdlib/src/io.rs +++ b/complexpr-stdlib/src/io.rs @@ -34,8 +34,10 @@ fn fn_input(_: Vec) -> Result { stdin.read_line(&mut buffer).map_err(|e| e.to_string())?; if buffer.ends_with('\n') { buffer.pop(); + Ok(Value::from(buffer)) + } else { + Ok(Value::Nil) } - Ok(Value::from(buffer)) } struct FileBox { diff --git a/complexpr-stdlib/src/iter.rs b/complexpr-stdlib/src/iter.rs index 48c220a..6b8ed95 100644 --- a/complexpr-stdlib/src/iter.rs +++ b/complexpr-stdlib/src/iter.rs @@ -14,6 +14,7 @@ pub fn load(env: &mut Environment) { declare_fn!(env, nth, 2); declare_fn!(env, last, 1); declare_fn!(env, void, 1); + declare_fn!(env, rev, 1); } fn fn_iter(mut args: Vec) -> Result { @@ -125,3 +126,12 @@ fn fn_void(args: Vec) -> Result { for _ in args[0].iter()? {} Ok(Value::Nil) } + +fn fn_rev(args: Vec) -> Result { + let mut lst = vec![]; + for item in args[0].iter()? { + lst.push(item?); + } + lst.reverse(); + Ok(Value::from(lst)) +} diff --git a/complexpr-stdlib/src/prelude.rs b/complexpr-stdlib/src/prelude.rs index 62765fc..d0a2abf 100644 --- a/complexpr-stdlib/src/prelude.rs +++ b/complexpr-stdlib/src/prelude.rs @@ -17,6 +17,11 @@ pub fn load(env: &mut Environment) { declare_fn!(env, repr, 1); declare_fn!(env, ord, 1); declare_fn!(env, chr, 1); + declare_fn!(env, to_radix, 2); + declare_fn!(env, bin, 1); + declare_fn!(env, hex, 1); + declare_fn!(env, sex, 1); + declare_fn!(env, from_radix, 2); declare_fn!(env, has, 2); declare_fn!(env, len, 1); declare_fn!(env, args, 0); @@ -93,6 +98,71 @@ fn fn_chr(args: Vec) -> Result { Err("Argument to chr must be an integer".into()) } } + +fn to_radix(r: i64, mut n: i64) -> String { + let mut result = String::new(); + let mut idx = 0; + if n == 0 { + result.push('0'); + return result + } else if n < 0 { + n = -n; + idx = 1; + result.push('-'); + } + while n != 0 { + let c = std::char::from_digit((n % r) as u32, r as u32).unwrap(); + result.insert(idx, c); + n /= r; + } + result +} + +fn fn_to_radix(args: Vec) -> Result { + match (&args[0], &args[1]) { + (Value::Int(r), Value::Int(n)) + if *r >= 2 && *r <= 36 => Ok(Value::from(to_radix(*r, *n))), + (Value::Int(_), Value::Int(_)) => Err(format!("Radix must be an integer in the range [2, 36]").into()), + (Value::Int(_), v) => Err(format!("Second argument to to_radix must be an integer, got {:?}", v).into()), + (v, _) => Err(format!("First argument to to_radix must be an integer, got {:?}", v).into()), + } +} + +fn fn_bin(args: Vec) -> Result { + match &args[0] { + Value::Int(n) => Ok(Value::from(to_radix(2, *n))), + v => Err(format!("Argument to bin must be an integer, got {:?}", v).into()), + } +} + +fn fn_hex(args: Vec) -> Result { + match &args[0] { + Value::Int(n) => Ok(Value::from(to_radix(16, *n))), + v => Err(format!("Argument to bin must be an integer, got {:?}", v).into()), + } +} + +fn fn_sex(args: Vec) -> Result { + match &args[0] { + Value::Int(n) => Ok(Value::from(to_radix(6, *n))), + v => Err(format!("Argument to bin must be an integer, got {:?}", v).into()), + } +} + +fn fn_from_radix(args: Vec) -> Result { + match (&args[0], &args[1]) { + (Value::Int(r), Value::String(s)) + if *r >= 2 && *r <= 36 + => match i64::from_str_radix(s, *r as u32) { + Ok(n) => Ok(Value::from(n)), + Err(_) => Err(format!("Failed to convert string to integer in specified radix").into()), + }, + (Value::Int(_), Value::String(_)) => Err(format!("Radix must be an integer in the range [2, 36]").into()), + (Value::Int(_), v) => Err(format!("Second argument to from_radix must be a string, got {:?}", v).into()), + (v, _) => Err(format!("First argument to from_radix must be an integer, got {:?}", v).into()), + } +} + fn fn_len(args: Vec) -> Result { Ok(Value::Int(args[0].len().map_err(RuntimeError::new_no_pos)? as i64)) } diff --git a/complexpr/src/eval.rs b/complexpr/src/eval.rs index 6fa2c8e..c6b5442 100644 --- a/complexpr/src/eval.rs +++ b/complexpr/src/eval.rs @@ -115,7 +115,7 @@ pub fn eval_stmt(stmt: &Stmt, env: EnvRef) -> Result<(), Unwind> { let name = name.ty.clone().as_ident().unwrap(); let func = Func::Func { name: Some(name.clone()), - args: args.iter().map(|a| a.ty.clone().as_ident().unwrap()).collect(), + args: args.to_vec(), env: env.clone(), func: Box::new(body.as_ref().clone()) }; @@ -219,7 +219,7 @@ pub fn eval_expr(expr: &Expr, env: EnvRef) -> Result { Expr::Fn { args, body } => { let func = Func::Func { name: None, - args: args.iter().map(|a| a.ty.clone().as_ident().unwrap()).collect(), + args: args.to_vec(), env, func: Box::new(body.as_ref().clone()) }; @@ -356,7 +356,7 @@ pub fn eval_assignment(lhs: &Expr, rhs: &Expr, op: &Token, env: EnvRef) -> Resul } pub fn eval_standard_binary(l: Value, r: Value, opty: &TokenType, pos: &Position) -> Result { - let mk_err = || format!("Cannot compare {:?} with {:?}", l, r); + let mk_err = || format!("Cannot compare {} with {}", l, r); match opty { TokenType::Plus => &l + &r, TokenType::Minus => &l - &r, @@ -460,21 +460,6 @@ fn eval_pipeline(l: Value, r: &Func, op: &Token) -> Result } Ok(result) }, - TokenType::PipeDoubleBackslash => { - let mut result = Value::Nil; - let mut first_iter = true; - let lst = l.iter().map_err(|e| RuntimeError::new(e, op.pos.clone()))?.collect::>>(); - for v in lst.into_iter().rev() { - let v = v.map_err(exit_pipe(&op.pos))?; - if first_iter { - result = v; - first_iter = false; - } else { - result = r.call(vec![v, result]).map_err(exit_pipe(&op.pos))?; - } - } - Ok(result) - }, _ => todo!() } @@ -495,19 +480,6 @@ pub fn eval_ternary(arg1: &Expr, arg2: &Expr, arg3: &Expr, op: &Token, env: EnvR } Ok(result) }, - TokenType::PipeBackslash => { - let iter = eval_expr(arg1, env.clone())?; - let mut result = eval_expr(arg2, env.clone())?; - let func = eval_expr(arg3, env)?; - let func = func.as_func() - .map_err(|e| RuntimeError::new(e, op.pos.clone()))?; - let lst = iter.iter().map_err(|e| RuntimeError::new(e, op.pos.clone()))?.collect::>>(); - for v in lst.into_iter().rev() { - let v = v.map_err(exit_pipe(&op.pos))?; - result = func.call(vec![v, result]).map_err(exit_pipe(&op.pos))?; - } - Ok(result) - }, _ => unreachable!() } } diff --git a/complexpr/src/expr.rs b/complexpr/src/expr.rs index a125a1b..0c070ec 100644 --- a/complexpr/src/expr.rs +++ b/complexpr/src/expr.rs @@ -13,7 +13,7 @@ pub enum Stmt { Break { pos: Position }, Continue { pos: Position }, Return { pos: Position, expr: Expr }, - Fn { name: Token, args: Vec, body: Box }, + Fn { name: Token, args: Vec>, body: Box }, StructDef { name: Token, ty: Type }, } @@ -47,7 +47,7 @@ pub enum Expr { Literal { value: Token }, List { items: Vec }, Map { items: Vec<(Expr,Expr)> }, - Fn { args: Vec, body: Box }, + Fn { args: Vec>, body: Box }, FuncCall { func: Box, args: Vec, pos: Position }, Index { lhs: Box, index: Box, pos: Position }, StructInit { ty: Box, args: Vec, pos: Position }, diff --git a/complexpr/src/lexer.rs b/complexpr/src/lexer.rs index 49afcf5..ac896a2 100644 --- a/complexpr/src/lexer.rs +++ b/complexpr/src/lexer.rs @@ -226,10 +226,6 @@ impl Lexer { Some('/') => self.add_token(TokenType::PipeDoubleSlash, "|//"), _ => self.add_token(TokenType::PipeSlash, "|/") }, - Some('\\') => match self.expect(&['\\']) { - Some('\\') => self.add_token(TokenType::PipeDoubleBackslash, "|\\\\"), - _ => self.add_token(TokenType::PipeBackslash, "|\\") - }, _ => self.add_token(TokenType::Pipe, "|"), }, '~' => self.add_token(TokenType::Tilde, "~"), diff --git a/complexpr/src/parser.rs b/complexpr/src/parser.rs index 666dc8d..6c49040 100644 --- a/complexpr/src/parser.rs +++ b/complexpr/src/parser.rs @@ -79,9 +79,9 @@ impl Parser { if self.peek().ty == TokenType::Comma { self.next(); } else if self.peek().ty == terminator { - break; + break } else { - return Err(self.mk_error(format!("Expected Comma or {:?} after list", terminator))) + return Err(self.mk_error(format!("Expected comma or {} after list", terminator))) } } self.err_on_eof()?; @@ -102,7 +102,11 @@ impl Parser { TokenType::Semicolon => { // skip lonely semicolon self.next(); - self.statement() + if self.at_end() { + Ok(Stmt::Block { stmts: vec![] }) + } else { + self.statement() + } } TokenType::Let => { // let statement @@ -249,6 +253,7 @@ impl Parser { return Err(self.mk_error("Expected left parenthesis to start arguments list")) } let args = self.commalist(TokenType::RParen, Self::ident)?; + let args = args.iter().map(|a| a.ty.clone().as_ident().unwrap()).collect(); self.err_on_eof()?; if self.peek().ty == TokenType::LParen { self.next(); @@ -333,7 +338,7 @@ impl Parser { while !self.at_end() && self.peek().ty.get_op_type() == Some(OpType::Pipeline) { let op = self.next(); let right = self.logical_or()?; - if op.ty == TokenType::PipeSlash || op.ty == TokenType::PipeBackslash { + if op.ty == TokenType::PipeSlash { self.err_on_eof()?; if !self.expect(TokenType::Comma).0 { return Err(self.mk_error("Expected comma after first argument")) @@ -508,7 +513,7 @@ impl Parser { self.err_on_eof()?; let next = self.next(); if matches!(next.ty, - TokenType::True | TokenType::False | TokenType::Nil + TokenType::True | TokenType::False | TokenType::Nil | TokenType::Int(_) | TokenType::Float(_) | TokenType::ImFloat(_) | TokenType::String(_) | TokenType::Char(_) ) { @@ -528,16 +533,17 @@ impl Parser { } else if next.ty == TokenType::Backslash { self.err_on_eof()?; let op = self.next(); - if !op.ty.is_infix_op() { - return Err(self.mk_error("Expected infix operator after backslash")) + if op.ty.is_infix_op() { + let func = Func::BuiltinClosure { + arg_count: 2, + func: Rc::new(move |args| { + eval_standard_binary(args[0].clone(), args[1].clone(), &op.ty, &op.pos) + }) + }; + Ok(Expr::BoxedInfix { func }) + } else { + Err(self.mk_error("Expected infix operator after backslash")) } - let func = Func::BuiltinClosure { - arg_count: 2, - func: Rc::new(move |args| { - eval_standard_binary(args[0].clone(), args[1].clone(), &op.ty, &op.pos) - }) - }; - Ok(Expr::BoxedInfix { func }) } else if next.ty == TokenType::LBrack { // list literal let items = self.commalist(TokenType::RBrack, Self::assignment)?; @@ -553,6 +559,7 @@ impl Parser { return Err(self.mk_error("Expected left parenthesis to start arguments list")) } let args = self.commalist(TokenType::RParen, Self::ident)?; + let args = args.iter().map(|a| a.ty.clone().as_ident().unwrap()).collect(); self.err_on_eof()?; if self.peek().ty == TokenType::LParen { self.next(); @@ -572,7 +579,7 @@ impl Parser { Err(self.mk_error("Expected '(' or '{' after function arguments list to begin body")) } } else { - Err(self.mk_error(format!("Unexpected token: {:?}", next.ty))) + Err(self.mk_error(format!("Unexpected token: {}", next.ty))) } } } diff --git a/complexpr/src/token.rs b/complexpr/src/token.rs index 3fa45e8..073780b 100644 --- a/complexpr/src/token.rs +++ b/complexpr/src/token.rs @@ -31,19 +31,92 @@ pub enum TokenType { DoubleEqual, BangEqual, Greater, GreaterEqual, Less, LessEqual, Spaceship, PipeColon, PipePoint, PipeQuestion, PipeAmper, - PipeSlash, PipeBackslash, PipeDoubleSlash, PipeDoubleBackslash, + PipeSlash, PipeDoubleSlash, Backslash, Comma, Semicolon, Colon, LParen, RParen, LBrack, RBrack, LBrace, RBrace, - True, False, Nil, + True, False, Nil, If, Elif, Else, For, While, Fn, Let, Struct, Break, Continue, Return } +impl fmt::Display for TokenType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TokenType::Int(n) => write!(f, "integer literal {}", n), + TokenType::Float(x) => write!(f, "float literal {}", x), + TokenType::ImFloat(x) => write!(f, "imaginary float literal {}i", x), + TokenType::String(s) => write!(f, "string literal {}", crate::value::Value::from(s.as_ref())), + TokenType::Char(c) => write!(f, "character literal {}", crate::value::Value::from(*c)), + TokenType::Ident(n) => write!(f, "identifier '{}'", n), + TokenType::Plus => f.write_str("operator +"), + TokenType::Minus => f.write_str("operator -"), + TokenType::Star => f.write_str("operator *"), + TokenType::Slash => f.write_str("operator /"), + TokenType::Percent => f.write_str("operator %"), + TokenType::DoubleSlash => f.write_str("operator //"), + TokenType::Caret => f.write_str("operator ^"), + TokenType::Bang => f.write_str("operator !"), + TokenType::DoubleAmper => f.write_str("operator &&"), + TokenType::DoublePipe => f.write_str("operator ||"), + TokenType::Tilde => f.write_str("operator ~"), + TokenType::Amper => f.write_str("operator &"), + TokenType::Pipe => f.write_str("operator |"), + TokenType::Dot => f.write_str("operator ."), + TokenType::DoubleDot => f.write_str("operator .."), + TokenType::Equal => f.write_str("operator ="), + TokenType::PlusEqual => f.write_str("operator +="), + TokenType::MinusEqual => f.write_str("operator -="), + TokenType::StarEqual => f.write_str("operator *="), + TokenType::SlashEqual => f.write_str("operator /="), + TokenType::PercentEqual => f.write_str("operator %="), + TokenType::DoubleSlashEqual => f.write_str("operator //="), + TokenType::CaretEqual => f.write_str("operator ^="), + TokenType::DoubleEqual => f.write_str("operator =="), + TokenType::BangEqual => f.write_str("operator !="), + TokenType::Greater => f.write_str("operator >"), + TokenType::GreaterEqual => f.write_str("operator >="), + TokenType::Less => f.write_str("operator <"), + TokenType::LessEqual => f.write_str("operator <="), + TokenType::Spaceship => f.write_str("operator <=>"), + TokenType::PipeColon => f.write_str("operator |:"), + TokenType::PipePoint => f.write_str("operator |>"), + TokenType::PipeQuestion => f.write_str("operator |?"), + TokenType::PipeAmper => f.write_str("operator |&"), + TokenType::PipeSlash => f.write_str("operator |/"), + TokenType::PipeDoubleSlash => f.write_str("operator |//"), + TokenType::Backslash => f.write_str("backslash"), + TokenType::Comma => f.write_str("comma"), + TokenType::Semicolon => f.write_str("semicolon"), + TokenType::Colon => f.write_str("colon"), + TokenType::LParen => f.write_str("left paren"), + TokenType::RParen => f.write_str("right paren"), + TokenType::LBrack => f.write_str("left bracket"), + TokenType::RBrack => f.write_str("right bracket"), + TokenType::LBrace => f.write_str("left brace"), + TokenType::RBrace => f.write_str("right brace"), + TokenType::True => f.write_str("literal 'true'"), + TokenType::False => f.write_str("literal 'false'"), + TokenType::Nil => f.write_str("literal 'nil'"), + TokenType::If => f.write_str("keyword 'if'"), + TokenType::Elif => f.write_str("keyword 'elif'"), + TokenType::Else => f.write_str("keyword 'else'"), + TokenType::For => f.write_str("keyword 'for'"), + TokenType::While => f.write_str("keyword 'while'"), + TokenType::Fn => f.write_str("keyword 'fn'"), + TokenType::Let => f.write_str("keyword 'let'"), + TokenType::Struct => f.write_str("keyword 'struct'"), + TokenType::Break => f.write_str("keyword 'break'"), + TokenType::Continue => f.write_str("keyword 'continue'"), + TokenType::Return => f.write_str("keyword 'return'"), + } + } +} + impl TokenType { pub fn get_op_type(&self) -> Option { match self { @@ -55,7 +128,7 @@ impl TokenType { Self::Caret => Some(OpType::Exponential), Self::PipeColon | Self::PipeAmper | Self::PipePoint | Self::PipeQuestion - | Self::PipeSlash | Self::PipeDoubleSlash | Self::PipeBackslash | Self::PipeDoubleBackslash => Some(OpType::Pipeline), + | Self::PipeSlash | Self::PipeDoubleSlash => Some(OpType::Pipeline), Self::Greater | Self::GreaterEqual | Self::Less | Self::LessEqual | Self::DoubleEqual | Self::BangEqual | Self::Spaceship => Some(OpType::Comparison), diff --git a/complexpr/src/value/mod.rs b/complexpr/src/value/mod.rs index 0403c9d..f7b7ed3 100644 --- a/complexpr/src/value/mod.rs +++ b/complexpr/src/value/mod.rs @@ -473,7 +473,7 @@ value_from!(Float, f32 f64); value_from!(Complex, Complex); value_from!(Rational, Rational); value_from!(Bool, bool); -value_from!(String, String Rc); +value_from!(String, String Rc &str); value_from!(List, RefCell>); value_from!(Char, char); value_from!(Map, RefCell>);