diff --git a/README.md b/README.md index dd7d840..29654b7 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ - [x] Bitwise NOT `~X` - [x] Bitwise left shift `X<>Y` -- [ ] Variables - - [ ] Declaration - - [ ] Assignment +- [x] Variables + - [x] Declaration + - [x] Assignment - [ ] Control flow - [ ] While loop `while X { ... }` - [ ] If else statement `if X { ... } else { ... }` @@ -52,7 +52,7 @@ ### Expressions ``` -expr_primary = LITERAL | "(" expr p | "-" expr_primary | "~" expr_primary +expr_primary = LITERAL | IDENT | "(" expr ")" | "-" expr_primary | "~" expr_primary expr_mul = expr_primary (("*" | "/" | "%") expr_primary)* expr_add = expr_mul (("+" | "-") expr_mul)* expr_shift = expr_add ((">>" | "<<") expr_add)* diff --git a/src/interpreter.rs b/src/interpreter.rs index 0c45d8d..4e36bbf 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::parser::{Ast, BinOpType, UnOpType}; #[derive(Debug, PartialEq, Eq, Clone)] @@ -6,12 +8,15 @@ pub enum Value { } pub struct Interpreter { - // Runtime storage, for example variables ... + // Variable table stores the runtime values of variables + vartable: HashMap, } impl Interpreter { pub fn new() -> Self { - Self {} + Self { + vartable: HashMap::new(), + } } pub fn run(&mut self, prog: Ast) { @@ -25,6 +30,14 @@ impl Interpreter { Ast::I64(val) => Value::I64(val), Ast::BinOp(bo, lhs, rhs) => self.resolve_binop(bo, *lhs, *rhs), Ast::UnOp(uo, operand) => self.resolve_unop(uo, *operand), + Ast::Var(name) => self.resolve_var(name), + } + } + + fn resolve_var(&mut self, name: String) -> Value { + match self.vartable.get(&name) { + Some(val) => val.clone(), + None => panic!("Variable '{}' used but not declared", name), } } @@ -39,9 +52,25 @@ impl Interpreter { } fn resolve_binop(&mut self, bo: BinOpType, lhs: Ast, rhs: Ast) -> Value { - let lhs = self.resolve_expr(lhs); let rhs = self.resolve_expr(rhs); + match (&bo, &lhs) { + (BinOpType::Declare, Ast::Var(name)) => { + self.vartable.insert(name.clone(), rhs.clone()); + return rhs; + } + (BinOpType::Assign, Ast::Var(name)) => { + match self.vartable.get_mut(name) { + Some(val) => *val = rhs.clone(), + None => panic!("Runtime Error: Trying to assign value to undeclared variable"), + } + return rhs; + } + _ => () + } + + let lhs = self.resolve_expr(lhs); + match (lhs, rhs) { (Value::I64(lhs), Value::I64(rhs)) => match bo { BinOpType::Add => Value::I64(lhs + rhs), @@ -60,6 +89,8 @@ impl Interpreter { BinOpType::LessEqu => Value::I64(if lhs <= rhs { 1 } else { 0 }), BinOpType::Greater => Value::I64(if lhs > rhs { 1 } else { 0 }), BinOpType::GreaterEqu => Value::I64(if lhs >= rhs { 1 } else { 0 }), + + BinOpType::Declare | BinOpType::Assign => unreachable!(), }, // _ => panic!("Value types are not compatible"), } diff --git a/src/lexer.rs b/src/lexer.rs index 08d310f..466dc21 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -7,6 +7,9 @@ pub enum Token { /// Integer literal (64-bit) I64(i64), + /// Identifier (name for variables, functions, ...) + Ident(String), + /// Left Parenthesis ('(') LParen, @@ -64,6 +67,12 @@ pub enum Token { /// Left angle bracket Equal (>=) RAngleEqu, + /// Left arrow (<-) + LArrow, + + /// Equal Sign (=) + Equ, + /// End of file EoF, } @@ -89,30 +98,6 @@ impl<'a> Lexer<'a> { // Stop lexing at EOF '\0' => break, - // Lex numbers - ch @ '0'..='9' => { - let mut sval = String::from(ch); - - // Do as long as a next char exists and it is a numeric char - loop { - // The next char is verified to be Some, so unwrap is safe - match self.peek() { - // Underscore is a separator, so remove it but don't add to number - '_' => { - self.next(); - } - '0'..='9' => { - sval.push(self.next()); - } - // Next char is not a number, so stop and finish the number token - _ => break, - } - } - - // TODO: We only added numeric chars to the string, but the conversion could still fail - tokens.push(Token::I64(sval.parse().unwrap())); - } - '>' if matches!(self.peek(), '>') => { self.next(); tokens.push(Token::Shr); @@ -137,6 +122,10 @@ impl<'a> Lexer<'a> { self.next(); tokens.push(Token::RAngleEqu); } + '<' if matches!(self.peek(), '-') => { + self.next(); + tokens.push(Token::LArrow); + } '+' => tokens.push(Token::Add), '-' => tokens.push(Token::Sub), @@ -151,6 +140,49 @@ impl<'a> Lexer<'a> { '~' => tokens.push(Token::Tilde), '<' => tokens.push(Token::LAngle), '>' => tokens.push(Token::RAngle), + '=' => tokens.push(Token::Equ), + + // Lex numbers + ch @ '0'..='9' => { + let mut sval = String::from(ch); + + // Do as long as a next char exists and it is a numeric char + loop { + // The next char is verified to be Some, so unwrap is safe + match self.peek() { + // Underscore is a separator, so remove it but don't add to number + '_' => { + self.next(); + } + '0'..='9' => { + sval.push(self.next()); + } + // Next char is not a number, so stop and finish the number token + _ => break, + } + } + + // TODO: We only added numeric chars to the string, but the conversion could still fail + tokens.push(Token::I64(sval.parse().unwrap())); + } + + // Lex characters as identifier + ch @ ('a'..='z' | 'A'..='Z' | '_') => { + let mut ident = String::from(ch); + + // Do as long as a next char exists and it is a valid char for an identifier + loop { + match self.peek() { + 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => { + ident.push(self.next()); + } + // Next char is not valid, so stop and finish the ident token + _ => break, + } + } + + tokens.push(Token::Ident(ident)); + } //TODO: Don't panic, keep calm ch => panic!("Lexer encountered unexpected char: '{}'", ch), @@ -205,6 +237,9 @@ impl Token { Token::RAngle => BinOpType::Greater, Token::RAngleEqu => BinOpType::GreaterEqu, + Token::LArrow => BinOpType::Declare, + Token::Equ => BinOpType::Assign, + _ => return None, }) } diff --git a/src/main.rs b/src/main.rs index d7500eb..8f88550 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,31 @@ +use std::io::Write; + use nek_lang::{lexer::lex, parser::parse, interpreter::Interpreter}; fn main() { - let mut code = String::new(); - - std::io::stdin().read_line(&mut code).unwrap(); - let code = code.trim(); - - let tokens = lex(&code); - - println!("Tokens: {:?}\n", tokens); - - let ast = parse(tokens); - - println!("Ast: {:#?}\n", ast); - let mut interpreter = Interpreter::new(); - interpreter.run(ast); + let mut code = String::new(); + + loop { + print!(">> "); + std::io::stdout().flush().unwrap(); + + code.clear(); + std::io::stdin().read_line(&mut code).unwrap(); + let code = code.trim(); + + let tokens = lex(&code); + + println!("Tokens: {:?}\n", tokens); + + let ast = parse(tokens); + + println!("Ast: {:#?}\n", ast); + + interpreter.run(ast); + } } diff --git a/src/parser.rs b/src/parser.rs index 168c599..2db475b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -52,6 +52,12 @@ pub enum BinOpType { /// Shift Right Shr, + + /// Assign value to variable + Assign, + + /// Declare new variable with value + Declare, } #[derive(Debug, PartialEq, Eq, Clone)] @@ -67,6 +73,8 @@ pub enum UnOpType { pub enum Ast { /// Integer literal (64-bit) I64(i64), + /// Variable + Var(String), /// Binary operation. Consists of type, left hand side and right hand side BinOp(BinOpType, Box, Box), /// Unary operation. Consists of type and operand @@ -127,6 +135,8 @@ impl> Parser { // Literal i64 Token::I64(val) => Ast::I64(val), + Token::Ident(name) => Ast::Var(name), + // Parentheses grouping Token::LParen => { let inner_expr = self.parse_expr(); @@ -177,6 +187,7 @@ impl BinOpType { /// The operator precedences are derived from the C language operator precedences. While not all /// C operators are included or the exact same, the precedence oder is the same. /// See: https://en.cppreference.com/w/c/language/operator_precedence + fn precedence(&self) -> u8 { match self { BinOpType::BOr => 0, @@ -187,6 +198,8 @@ impl BinOpType { BinOpType::Shl | BinOpType::Shr => 5, BinOpType::Add | BinOpType::Sub => 6, BinOpType::Mul | BinOpType::Div | BinOpType::Mod => 7, + BinOpType::Assign => 8, + BinOpType::Declare => 9, } } }