simple-vm 0.1.1

A simple bytecode VM with a custom compiler
Documentation
#[derive(Debug, PartialEq, Clone)]
pub enum Token {
    Number(i64),
    Plus,
    Minus,
    Star,
    Slash,
    LParen,
    RParen,
    Semicolon,
    Equals,
    Identifier(String),
    Let,
    If,
    Else,
    While,
    Print,
    DoubleEquals,
    LessThan,
    GreaterThan,
}

pub struct Lexer {
    input: Vec<char>,
    position: usize,
}

impl Lexer {
    pub fn new(input: &str) -> Self {
        Lexer {
            input: input.chars().collect(),
            position: 0,
        }
    }

    fn peek(&self) -> Option<char> {
        self.input.get(self.position).copied()
    }

    fn advance(&mut self) -> Option<char> {
        if self.position < self.input.len() {
            let ch = self.input[self.position];
            self.position += 1;
            Some(ch)
        } else {
            None
        }
    }

    fn skip_whitespace(&mut self) {
        while let Some(ch) = self.peek() {
            if !ch.is_whitespace() {
                break;
            }
            self.advance();
        }
    }

    fn read_number(&mut self) -> Token {
        let mut number = String::new();
        while let Some(ch) = self.peek() {
            if !ch.is_digit(10) {
                break;
            }
            number.push(ch);
            self.advance();
        }
        Token::Number(number.parse().unwrap())
    }

    fn read_identifier(&mut self) -> Token {
        let mut ident = String::new();
        while let Some(ch) = self.peek() {
            if !ch.is_alphanumeric() && ch != '_' {
                break;
            }
            ident.push(ch);
            self.advance();
        }
        
        match ident.as_str() {
            "let" => Token::Let,
            "if" => Token::If,
            "else" => Token::Else,
            "while" => Token::While,
            "print" => Token::Print,
            _ => Token::Identifier(ident),
        }
    }

    pub fn next_token(&mut self) -> Option<Token> {
        self.skip_whitespace();
        
        let ch = self.peek()?;
        match ch {
            '0'..='9' => Some(self.read_number()),
            'a'..='z' | 'A'..='Z' | '_' => Some(self.read_identifier()),
            '+' => { self.advance(); Some(Token::Plus) },
            '-' => { self.advance(); Some(Token::Minus) },
            '*' => { self.advance(); Some(Token::Star) },
            '/' => { self.advance(); Some(Token::Slash) },
            '(' => { self.advance(); Some(Token::LParen) },
            ')' => { self.advance(); Some(Token::RParen) },
            ';' => { self.advance(); Some(Token::Semicolon) },
            '=' => {
                self.advance();
                if self.peek() == Some('=') {
                    self.advance();
                    Some(Token::DoubleEquals)
                } else {
                    Some(Token::Equals)
                }
            },
            '<' => { self.advance(); Some(Token::LessThan) },
            '>' => { self.advance(); Some(Token::GreaterThan) },
            _ => None,
        }
    }
}