From 3d915a5b94f70449476d758eb673a574b6267395 Mon Sep 17 00:00:00 2001 From: TriMill Date: Wed, 7 Sep 2022 18:10:43 -0400 Subject: [PATCH] for loops and character type --- idea.cxpr | 4 ++++ src/eval.rs | 37 +++++++++++++++++++++++++++++------ src/expr.rs | 3 ++- src/lexer.rs | 26 ++++++++++++++++++++++--- src/parser.rs | 51 +++++++++++++++++++++++++++++++++++-------------- src/token.rs | 4 ++-- src/value.rs | 25 +++++++++++++++++++++++- tests/code.cxpr | 7 ++----- 8 files changed, 125 insertions(+), 32 deletions(-) diff --git a/idea.cxpr b/idea.cxpr index f68f23f..ebac261 100644 --- a/idea.cxpr +++ b/idea.cxpr @@ -14,6 +14,10 @@ if 49 <~ data { println("49 is not the square of a prime"); } +for p : primes { + println(p); +} + # LET IDENT(square) EQ IDENT(x) ARROW IDENT(x) CARET INT(2) SEMICOLON # # LET IDENT(data) EQ IDENT(range) LPAREN INT(100) RPAREN PIPECOLON IDENT(filter) LPAREN IDENT(is_prime) RPAREN PIPECOLON IDENT(map) LPAREN IDENT(square) RPAREN SEMICOLON diff --git a/src/eval.rs b/src/eval.rs index 51fdbba..14ba74d 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -55,22 +55,30 @@ impl Environment { } } +fn unwrap_ident_token<'a>(tok: &'a Token) -> &'a Rc { + if let Token { ty: TokenType::Ident(s),.. } = tok { + s + } else { + unreachable!("precondition failed") + } +} + pub fn eval_stmt(stmt: &Stmt, env: EnvRef) -> Result<(), RuntimeError> { match stmt { Stmt::Expr{ expr } => drop(eval_expr(expr, env)), - Stmt::Let { lhs: Token{ty: TokenType::Ident(s),..}, rhs: None } - => env.borrow_mut().declare(s.clone(), Value::Nil), - Stmt::Let { lhs: Token{ty: TokenType::Ident(s),..}, rhs: Some(rhs) } => { + Stmt::Let { lhs, rhs: None } + => env.borrow_mut().declare(unwrap_ident_token(lhs).clone(), Value::Nil), + Stmt::Let { lhs, rhs: Some(rhs) } => { let r = eval_expr(rhs, env.clone())?; - env.borrow_mut().declare(s.clone(), r) + env.borrow_mut().declare(unwrap_ident_token(lhs).clone(), r) }, Stmt::Block { stmts } => { let block_env = Environment::extend(env).wrap(); for stmt in stmts { eval_stmt(stmt, block_env.clone())? } - } + }, Stmt::If { if_clauses, else_clause } => { for ic in if_clauses { let cond = eval_expr(&ic.0, env.clone())?; @@ -81,7 +89,21 @@ pub fn eval_stmt(stmt: &Stmt, env: EnvRef) -> Result<(), RuntimeError> { if let Some(ec) = else_clause { return eval_stmt(&ec, env) } - } + }, + Stmt::For { var, expr, stmt } => { + let name = unwrap_ident_token(var); + let iter = eval_expr(expr, env.clone())?; + env.borrow_mut().declare(name.clone(), Value::Nil); + if let Ok(i) = iter.iter() { + for v in i { + let env = env.clone(); + env.borrow_mut().set(name.clone(), v).expect("unreachable"); + eval_stmt(&stmt, env)?; + } + } else { + return Err(RuntimeError { message: "Cannot iterate this type".into(), pos: var.pos.clone() }) + }; + }, _ => unreachable!() } Ok(()) @@ -111,6 +133,7 @@ pub fn eval_literal(token: &Token) -> Value { TokenType::Float(f) => Value::Float(*f), TokenType::ImFloat(f) => Value::Complex(Complex64::new(0.0, *f)), TokenType::String(s) => Value::String(s.clone()), + TokenType::Char(c) => Value::Char(*c), _ => todo!() } } @@ -147,6 +170,7 @@ pub fn eval_assignment(lhs: &Box, rhs: &Box, op: &Token, env: EnvRef TokenType::MinusEqual => &prev_value - &r, TokenType::StarEqual => &prev_value * &r, TokenType::SlashEqual => &prev_value / &r, + TokenType::PercentEqual => &prev_value % &r, _ => todo!() // TODO more operations }.map_err(|e| RuntimeError { message: e, pos: op.pos.clone() })?; @@ -167,6 +191,7 @@ pub fn eval_binary(lhs: &Box, rhs: &Box, op: &Token, env: EnvRef) -> TokenType::Minus => &l - &r, TokenType::Star => &l * &r, TokenType::Slash => &l / &r, + TokenType::Percent => &l % &r, _ => todo!() // TODO other operations }.map_err(|e| RuntimeError { message: e, pos: op.pos.clone() }) } \ No newline at end of file diff --git a/src/expr.rs b/src/expr.rs index d24f052..75006dc 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -6,7 +6,8 @@ pub enum Stmt { Expr { expr: Expr }, Let { lhs: Token, rhs: Option }, Block { stmts: Vec }, - If { if_clauses: Vec<(Expr, Stmt)>, else_clause: Option> } + If { if_clauses: Vec<(Expr, Stmt)>, else_clause: Option> }, + For { var: Token, expr: Expr, stmt: Box } } impl fmt::Debug for Stmt { diff --git a/src/lexer.rs b/src/lexer.rs index 8320e7c..04c7b2b 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -65,6 +65,10 @@ impl Lexer { message: msg.into() } } + + fn collect_literal(&self) -> String { + self.code[self.start..self.current].iter().collect::() + } fn advance(&mut self, newline: bool) { if newline { @@ -139,6 +143,7 @@ impl Lexer { }, ',' => self.add_token(TokenType::Comma, ","), ';' => self.add_token(TokenType::Semicolon, ";"), + ':' => self.add_token(TokenType::Colon, ":"), '(' => self.add_token(TokenType::LParen, "("), ')' => self.add_token(TokenType::RParen, ")"), '[' => self.add_token(TokenType::LBrack, "["), @@ -152,6 +157,7 @@ impl Lexer { self.advance(true); }, '"' => self.string()?, + '\'' => self.char()?, ' ' | '\t' | '\r' | '\n' => (), '0'..='9' => self.number()?, 'a'..='z' | 'A'..='Z' | '_' => self.ident()?, @@ -161,6 +167,20 @@ impl Lexer { Ok(()) } + fn char(&mut self) -> Result<(), ParserError> { + if self.at_end() { return Err(self.mk_error("Unexpected EOF in character literal")) } + let mut c = self.next(); + if c == '\'' { + return Err(self.mk_error("Empty character literal")) + } else if c == '\\' { + if self.at_end() { return Err(self.mk_error("Unexpected EOF in character literal")) } + // TODO Escapes + } + self.expect(&['\'']).ok_or(self.mk_error("Expected ' to terminate character literal"))?; + self.add_token(TokenType::Char(c), self.collect_literal()); + Ok(()) + } + fn string(&mut self) -> Result<(), ParserError> { let mut s = String::new(); while !self.at_end() && self.peek() != '"' { @@ -188,7 +208,7 @@ impl Lexer { return Err(self.mk_error("Unexpected EOF while parsing string")) } self.advance(false); - self.add_token(TokenType::String(Rc::from(s)), self.code[self.start..self.current].iter().collect::()); + self.add_token(TokenType::String(Rc::from(s)), self.collect_literal()); Ok(()) } @@ -206,7 +226,7 @@ impl Lexer { } let is_imag = !self.at_end() && self.peek() == 'i'; if is_imag { self.advance(false); } - let literal = self.code[self.start..self.current].iter().collect::(); + let literal = self.collect_literal(); if is_imag { match literal[..literal.len()-1].parse::() { Ok(num) => self.add_token(TokenType::ImFloat(num), literal), @@ -230,7 +250,7 @@ impl Lexer { while !self.at_end() && (self.peek().is_ascii_alphanumeric() || self.peek() == '_') { self.advance(false); } - let literal = self.code[self.start..self.current].iter().collect::(); + let literal = self.collect_literal(); let token_ty = match literal.as_ref() { "true" => TokenType::True, "false" => TokenType::False, diff --git a/src/parser.rs b/src/parser.rs index 38c5c8e..7547373 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -54,24 +54,28 @@ impl Parser { let next = self.peek(); match next.ty { - // let statement TokenType::Let => { + // let statement self.next(); - return self.letstmt() - } + self.letstmt() + }, TokenType::LBrace => { + // block self.next(); - return self.block() - } - - // if statement + self.block() + }, TokenType::If => { + // if statement self.next(); - return self.ifstmt() - } - - // fallback to an expression terminated with a semicolon + self.ifstmt() + }, + TokenType::For => { + // for statement + self.next(); + self.forstmt() + }, _ => { + // fallback to an expression terminated with a semicolon let expr = self.assignment()?; if self.at_end() { if self.repl { @@ -83,10 +87,10 @@ impl Parser { let next = self.next(); - return match next.ty { + match next.ty { TokenType::Semicolon => Ok(Stmt::Expr{expr}), _ => Err(self.mk_error("Missing semicolon after statement")) - }; + } } } } @@ -154,6 +158,25 @@ impl Parser { }) } + fn forstmt(&mut self) -> Result { + self.err_on_eof()?; + let var = self.next(); + if let TokenType::Ident(_) = &var.ty { + self.err_on_eof()?; + let x = self.next(); + if x.ty != TokenType::Colon { + return Err(self.mk_error("Expected colon")) + } + self.err_on_eof()?; + let expr = self.assignment()?; + self.err_on_eof()?; + let stmt = self.statement()?; + Ok(Stmt::For{ var, expr, stmt: Box::new(stmt) }) + } else { + Err(self.mk_error("Expected identifier after for")) + } + } + fn block(&mut self) -> Result { let mut stmts = vec![]; while !self.at_end() && self.peek().ty != TokenType::RBrace { @@ -263,7 +286,7 @@ impl Parser { if matches!(next.ty, TokenType::True | TokenType::False | TokenType::Nil | TokenType::Int(_) | TokenType::Float(_) | TokenType::ImFloat(_) - | TokenType::String(_) + | TokenType::String(_) | TokenType::Char(_) ) { Ok(Expr::Literal { value: next }) } else if let TokenType::Ident(..) = next.ty { diff --git a/src/token.rs b/src/token.rs index 2e9199c..4d7232d 100644 --- a/src/token.rs +++ b/src/token.rs @@ -18,7 +18,7 @@ impl fmt::Debug for Token { #[derive(Clone, Debug, PartialEq)] pub enum TokenType { - Int(i64), Float(f64), ImFloat(f64), String(Rc), + Int(i64), Float(f64), ImFloat(f64), String(Rc), Char(char), Ident(Rc), Plus, Minus, Star, Slash, Percent, DoubleSlash, Caret, @@ -29,7 +29,7 @@ pub enum TokenType { Arrow, PipeColon, PipePoint, PipeQuestion, PipeAmper, - Comma, Semicolon, + Comma, Semicolon, Colon, LParen, RParen, LBrack, RBrack, LBrace, RBrace, diff --git a/src/value.rs b/src/value.rs index fb2981d..82589aa 100644 --- a/src/value.rs +++ b/src/value.rs @@ -1,4 +1,4 @@ -use std::{rc::Rc, collections::HashMap, ops::*}; +use std::{rc::Rc, collections::HashMap, ops::*, ascii::AsciiExt}; use num_traits::Zero; @@ -54,6 +54,7 @@ pub enum Value { Nil, Int(i64), Float(f64), Complex(Complex), Rational(Rational), Bool(bool), + Char(char), String(Rc), List(Rc>), Map(Rc>), } @@ -72,6 +73,16 @@ impl Value { _ => true } } + + pub fn iter(&self) -> Result + '_>, ()> { + match self { + Value::String(s) + => Ok(Box::new(s.chars() + .map(|c| Value::Char(c)))), + Value::List(l) => Ok(Box::new(l.iter().cloned())), + _ => Err(()) + } + } } @@ -81,11 +92,23 @@ value_from!(Complex, Complex); value_from!(Rational, Rational); value_from!(Bool, bool); value_from!(String, String Rc); +value_from!(Char, char); value_from!(List, Vec); value_from!(Map, HashMap); impl_numeric_op!(Add, add, { (String(a), String(b)) => Ok(((**a).to_owned() + b).into()), + (String(a), Char(c)) => { + let mut s = (**a).to_owned(); + s.push(*c); + Ok(s.into()) + }, + (Char(c), String(a)) => Ok((c.to_string() + a).into()), + (Char(c1), Char(c2)) => { + let mut s = c1.to_string(); + s.push(*c2); + Ok(s.into()) + } (List(a), List(b)) => { let mut a = (**a).clone(); a.append(&mut (**b).clone()); diff --git a/tests/code.cxpr b/tests/code.cxpr index a4be086..22e8a87 100644 --- a/tests/code.cxpr +++ b/tests/code.cxpr @@ -1,7 +1,4 @@ -if false { -} elif true { - -} else { - +for c: "asdfghjkl" { + let b = 'q' + c; } \ No newline at end of file