Implement variables

- Assignment
- Declaration
- Identifier lexing
This commit is contained in:
Daniel M 2022-01-29 22:49:15 +01:00
parent 07636d420c
commit 14e8a0b507
5 changed files with 132 additions and 45 deletions

View File

@ -33,9 +33,9 @@
- [x] Bitwise NOT `~X`
- [x] Bitwise left shift `X<<Y`
- [x] Bitwise right 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)*

View File

@ -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<String, Value>,
}
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"),
}

View File

@ -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,
})
}

View File

@ -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);
}
}

View File

@ -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<Ast>, Box<Ast>),
/// Unary operation. Consists of type and operand
@ -127,6 +135,8 @@ impl<T: Iterator<Item = Token>> Parser<T> {
// 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,
}
}
}