Add `let` statement parser
This commit is contained in:
commit
a67d4cb273
|
@ -0,0 +1 @@
|
|||
/target
|
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "monkeyrs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
|
@ -0,0 +1,69 @@
|
|||
use std::{fmt::Debug, rc::Rc};
|
||||
|
||||
use crate::token::Token;
|
||||
|
||||
pub trait Node: Debug {}
|
||||
|
||||
pub trait Statement: Node {
|
||||
fn statement_node(&self);
|
||||
}
|
||||
|
||||
pub trait Expression: Node {
|
||||
fn expression_node(&self);
|
||||
}
|
||||
|
||||
pub struct Program {
|
||||
pub statements: Vec<Rc<dyn Statement>>,
|
||||
}
|
||||
|
||||
impl Node for Program {}
|
||||
|
||||
impl Debug for Program {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Vec<")?;
|
||||
for statement in &self.statements {
|
||||
write!(f, "Box<")?;
|
||||
statement.fmt(f)?;
|
||||
write!(f, ">, ")?;
|
||||
}
|
||||
write!(f, ">")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LetStatement {
|
||||
pub token: Token,
|
||||
pub name: Identifier,
|
||||
pub value: Box<dyn Expression>,
|
||||
}
|
||||
|
||||
impl Node for LetStatement {}
|
||||
|
||||
impl Statement for LetStatement {
|
||||
fn statement_node(&self) {}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Identifier {
|
||||
pub token: Token,
|
||||
}
|
||||
|
||||
impl Node for Identifier {}
|
||||
|
||||
impl Expression for Identifier {
|
||||
fn expression_node(&self) {}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DummyExpression {}
|
||||
|
||||
impl Node for DummyExpression {}
|
||||
|
||||
impl Expression for DummyExpression {
|
||||
fn expression_node(&self) {
|
||||
panic!("this is dummy");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,251 @@
|
|||
use crate::token::Token;
|
||||
|
||||
pub struct Lexer {
|
||||
source: String,
|
||||
position: usize,
|
||||
read_pos: usize,
|
||||
ch: char,
|
||||
}
|
||||
|
||||
impl Lexer {
|
||||
pub fn new(source: String) -> Self {
|
||||
let mut lexer = Self {
|
||||
source,
|
||||
position: 0,
|
||||
read_pos: 0,
|
||||
ch: char::from_u32(0).unwrap(),
|
||||
};
|
||||
|
||||
lexer.read_char();
|
||||
|
||||
lexer
|
||||
}
|
||||
|
||||
fn read_char(&mut self) {
|
||||
self.ch = if self.read_pos >= self.source.chars().count() {
|
||||
char::from_u32(0).unwrap()
|
||||
} else {
|
||||
self.source.chars().nth(self.read_pos).unwrap()
|
||||
};
|
||||
|
||||
self.position = self.read_pos;
|
||||
self.read_pos += 1;
|
||||
}
|
||||
|
||||
fn read_identifier(&mut self) -> Token {
|
||||
let pos = self.position;
|
||||
|
||||
while is_letter(self.ch) {
|
||||
self.read_char();
|
||||
}
|
||||
|
||||
let literal = &self.source[pos..self.position];
|
||||
|
||||
return Token::lookup_ident(literal);
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) {
|
||||
while self.ch == ' ' || self.ch == '\t' || self.ch == '\n' || self.ch == '\t' {
|
||||
self.read_char();
|
||||
}
|
||||
}
|
||||
|
||||
fn read_number(&mut self) -> Token {
|
||||
let pos = self.position;
|
||||
|
||||
while is_digit(self.ch) {
|
||||
self.read_char();
|
||||
}
|
||||
|
||||
Token::Int(self.source[pos..self.position].parse().unwrap())
|
||||
}
|
||||
|
||||
fn peek_char(&self) -> char {
|
||||
if self.read_pos >= self.source.chars().count() {
|
||||
char::from_u32(0).unwrap()
|
||||
} else {
|
||||
self.source.chars().nth(self.read_pos).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Lexer {
|
||||
type Item = Token;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
use Token::*;
|
||||
|
||||
self.skip_whitespace();
|
||||
|
||||
let token = match self.ch {
|
||||
'=' => {
|
||||
if self.peek_char() == '=' {
|
||||
self.read_char();
|
||||
Eq
|
||||
} else {
|
||||
Assign
|
||||
}
|
||||
}
|
||||
';' => Semicolon,
|
||||
'(' => Lparen,
|
||||
')' => Rparen,
|
||||
',' => Comma,
|
||||
'+' => Plus,
|
||||
'-' => Minus,
|
||||
'!' => {
|
||||
if self.peek_char() == '=' {
|
||||
self.read_char();
|
||||
NotEq
|
||||
} else {
|
||||
Bang
|
||||
}
|
||||
}
|
||||
'/' => Slash,
|
||||
'*' => Asterisk,
|
||||
'<' => Lt,
|
||||
'>' => Gt,
|
||||
'{' => Lbrace,
|
||||
'}' => Rbrace,
|
||||
c if c as u32 == 0 => EOF,
|
||||
_ => {
|
||||
let tok = if is_letter(self.ch) {
|
||||
self.read_identifier()
|
||||
} else if is_digit(self.ch) {
|
||||
self.read_number()
|
||||
} else {
|
||||
Illegal
|
||||
};
|
||||
return Some(tok);
|
||||
}
|
||||
};
|
||||
|
||||
self.read_char();
|
||||
|
||||
Some(token)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_letter(ch: char) -> bool {
|
||||
'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
|
||||
}
|
||||
|
||||
fn is_digit(ch: char) -> bool {
|
||||
'0' <= ch && ch <= '9'
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::lexer::Lexer;
|
||||
use crate::token::Token;
|
||||
|
||||
#[test]
|
||||
fn test_next_token() {
|
||||
let input = "let five = 5;\
|
||||
let ten = 10;\
|
||||
\
|
||||
let add = fn(x, y) {\
|
||||
x + y;\
|
||||
};\
|
||||
\
|
||||
let result = add(five, ten);\
|
||||
!-/*5;
|
||||
5 < 10 > 5;
|
||||
if (5 < 10) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
10 == 10;
|
||||
10 != 9;
|
||||
"
|
||||
.to_string();
|
||||
|
||||
use Token::*;
|
||||
|
||||
let tests = vec![
|
||||
Let,
|
||||
Ident("five".to_string()),
|
||||
Assign,
|
||||
Int(5),
|
||||
Semicolon,
|
||||
Let,
|
||||
Ident("ten".to_string()),
|
||||
Assign,
|
||||
Int(10),
|
||||
Semicolon,
|
||||
Let,
|
||||
Ident("add".to_string()),
|
||||
Assign,
|
||||
Function,
|
||||
Lparen,
|
||||
Ident("x".to_string()),
|
||||
Comma,
|
||||
Ident("y".to_string()),
|
||||
Rparen,
|
||||
Lbrace,
|
||||
Ident("x".to_string()),
|
||||
Plus,
|
||||
Ident("y".to_string()),
|
||||
Semicolon,
|
||||
Rbrace,
|
||||
Semicolon,
|
||||
Let,
|
||||
Ident("result".to_string()),
|
||||
Assign,
|
||||
Ident("add".to_string()),
|
||||
Lparen,
|
||||
Ident("five".to_string()),
|
||||
Comma,
|
||||
Ident("ten".to_string()),
|
||||
Rparen,
|
||||
Semicolon,
|
||||
Bang,
|
||||
Minus,
|
||||
Slash,
|
||||
Asterisk,
|
||||
Int(5),
|
||||
Semicolon,
|
||||
Int(5),
|
||||
Lt,
|
||||
Int(10),
|
||||
Gt,
|
||||
Int(5),
|
||||
Semicolon,
|
||||
If,
|
||||
Lparen,
|
||||
Int(5),
|
||||
Lt,
|
||||
Int(10),
|
||||
Rparen,
|
||||
Lbrace,
|
||||
Return,
|
||||
True,
|
||||
Semicolon,
|
||||
Rbrace,
|
||||
Else,
|
||||
Lbrace,
|
||||
Return,
|
||||
False,
|
||||
Semicolon,
|
||||
Rbrace,
|
||||
Int(10),
|
||||
Eq,
|
||||
Int(10),
|
||||
Semicolon,
|
||||
Int(10),
|
||||
NotEq,
|
||||
Int(9),
|
||||
Semicolon,
|
||||
EOF,
|
||||
];
|
||||
|
||||
let mut lexer_it = Lexer::new(input);
|
||||
|
||||
for (i, tt) in tests.iter().enumerate() {
|
||||
let token = lexer_it.next();
|
||||
|
||||
println!("{i}");
|
||||
assert_eq!(*tt, token.unwrap());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
mod ast;
|
||||
mod lexer;
|
||||
mod repl;
|
||||
mod token;
|
||||
mod parser;
|
||||
|
||||
fn main() {
|
||||
let stdout = std::io::stdout();
|
||||
let stdin = std::io::stdin();
|
||||
repl::start(stdout, stdin);
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use crate::{lexer::Lexer, token::Token, ast::{Program, Statement, LetStatement, Identifier, DummyExpression}};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
UnexpectedToken{
|
||||
expected: Token,
|
||||
actual: Option<Token>,
|
||||
},
|
||||
}
|
||||
|
||||
struct Parser {
|
||||
lexer: Lexer,
|
||||
cur_token: Option<Token>,
|
||||
peek_token: Option<Token>,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(mut lexer: Lexer) -> Self {
|
||||
let cur_token = lexer.next();
|
||||
let peek_token = lexer.next();
|
||||
Self { lexer, cur_token, peek_token }
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) -> Result<Program> {
|
||||
let mut program = Program {
|
||||
statements: Vec::new(),
|
||||
};
|
||||
|
||||
|
||||
let mut done = Some(());
|
||||
|
||||
while done.is_some() {
|
||||
let stmt = self.parse_statement();
|
||||
program.statements.push(stmt?);
|
||||
|
||||
done = self.next();
|
||||
}
|
||||
|
||||
Ok(program)
|
||||
}
|
||||
|
||||
fn parse_statement(&mut self) -> Result<Rc<dyn Statement>> {
|
||||
match &self.cur_token {
|
||||
Some(token) => {
|
||||
use Token::*;
|
||||
match token {
|
||||
Let => self.parse_let_statement(),
|
||||
t => unimplemented!("{t:?} statement token not impl"),
|
||||
}
|
||||
}
|
||||
None => unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_let_statement(&mut self) -> Result<Rc<dyn Statement>> {
|
||||
let token = self.cur_token.clone().unwrap();
|
||||
|
||||
self.expect_peek(&Token::Ident("".to_string()))?;
|
||||
|
||||
let name = Identifier {
|
||||
token: self.cur_token.clone().unwrap(),
|
||||
};
|
||||
|
||||
self.expect_peek(&Token::Assign)?;
|
||||
|
||||
while !self.cur_token_is(&Token::Semicolon) {
|
||||
self.next();
|
||||
}
|
||||
|
||||
Ok(Rc::new(LetStatement {
|
||||
token,
|
||||
name,
|
||||
value: Box::new(DummyExpression {}),
|
||||
}))
|
||||
}
|
||||
|
||||
fn expect_peek(&mut self, token: &Token) -> Result<()> {
|
||||
if self.peek_token.is_none() {
|
||||
return Err(Error::UnexpectedToken { expected: token.clone(), actual: None });
|
||||
}
|
||||
|
||||
let peek_token = self.peek_token.clone().unwrap();
|
||||
|
||||
if token.is_same_type(&peek_token) {
|
||||
self.next();
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::UnexpectedToken { expected: token.clone(), actual: None })
|
||||
}
|
||||
}
|
||||
|
||||
fn peek_token_is(&self, token: &Token) -> bool {
|
||||
if self.peek_token.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.peek_token.clone().unwrap().is_same_type(token)
|
||||
}
|
||||
|
||||
fn cur_token_is(&self, token: &Token) -> bool {
|
||||
if self.cur_token.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.cur_token.clone().unwrap().is_same_type(token)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Parser {
|
||||
type Item = ();
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let peek_token = self.lexer.next();
|
||||
|
||||
|
||||
self.cur_token = self.peek_token.clone();
|
||||
self.peek_token = peek_token;
|
||||
|
||||
match self.cur_token {
|
||||
None | Some(Token::EOF) => None,
|
||||
Some(_) => Some(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{lexer::Lexer, ast::Statement};
|
||||
|
||||
use super::Parser;
|
||||
|
||||
#[test]
|
||||
fn let_statements() {
|
||||
let source = "let x = 5;\
|
||||
let y = 10;\
|
||||
let foobar = 838383;\
|
||||
".to_string();
|
||||
|
||||
let lexer = Lexer::new(source);
|
||||
|
||||
let mut parser = Parser::new(lexer);
|
||||
|
||||
let program = parser.parse().unwrap();
|
||||
|
||||
assert_eq!(program.statements.len(), 3);
|
||||
|
||||
let expected_identifiers = vec![
|
||||
"x",
|
||||
"y",
|
||||
"foobar",
|
||||
];
|
||||
let mut statements_iter = program.statements.iter();
|
||||
for tt in expected_identifiers {
|
||||
let statement = statements_iter.next().unwrap();
|
||||
test_let_statement(statement.clone(), tt);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_let_statement(stmt: Rc<dyn Statement>, name: &str) {
|
||||
assert_eq!(
|
||||
format!("{stmt:?}"),
|
||||
format!("LetStatement {{ token: Let, name: Identifier {{ token: Ident(\"{name}\") }}, value: DummyExpression }}"),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
|
||||
use crate::{lexer::Lexer, token::Token};
|
||||
|
||||
const PROMPT: &str = ">> ";
|
||||
|
||||
pub fn start(mut w: impl Write, r: impl Read) {
|
||||
let mut reader = BufReader::new(r);
|
||||
loop {
|
||||
write!(w, "{PROMPT}").unwrap();
|
||||
w.flush().unwrap();
|
||||
|
||||
let mut line = String::new();
|
||||
reader.read_line(&mut line).unwrap();
|
||||
|
||||
if line.len() == 0 {
|
||||
writeln!(w, "").unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
let lex = Lexer::new(line);
|
||||
|
||||
for token in lex {
|
||||
if token == Token::EOF {
|
||||
break;
|
||||
}
|
||||
writeln!(w, "{token:?}").unwrap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Token {
|
||||
Illegal,
|
||||
EOF,
|
||||
|
||||
Ident(String),
|
||||
Int(i64),
|
||||
|
||||
Assign,
|
||||
Plus,
|
||||
Minus,
|
||||
Bang,
|
||||
Asterisk,
|
||||
Slash,
|
||||
|
||||
Lt,
|
||||
Gt,
|
||||
Eq,
|
||||
NotEq,
|
||||
|
||||
Comma,
|
||||
Semicolon,
|
||||
|
||||
Lparen,
|
||||
Rparen,
|
||||
Lbrace,
|
||||
Rbrace,
|
||||
|
||||
Function,
|
||||
Let,
|
||||
True,
|
||||
False,
|
||||
If,
|
||||
Else,
|
||||
Return,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn is_same_type(&self, other: &Token) -> bool {
|
||||
use Token::*;
|
||||
match self {
|
||||
Ident(_) => if let Ident(_) = other { true } else { false },
|
||||
Int(_) => if let Int(_) = other { true } else { false },
|
||||
tok => tok == other,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup_ident(ident: &str) -> Token {
|
||||
use Token::*;
|
||||
match ident {
|
||||
"fn" => Function,
|
||||
"let" => Let,
|
||||
"true" => True,
|
||||
"false" => False,
|
||||
"if" => If,
|
||||
"else" => Else,
|
||||
"return" => Return,
|
||||
ident => Ident(ident.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue