parser: initial expression parsing logic

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2023-08-13 07:37:14 +05:30
parent ad57170010
commit f7ec10646b
5 changed files with 437 additions and 91 deletions

View File

@@ -9,6 +9,7 @@ use crate::lexer::{
use std::rc::Rc; use std::rc::Rc;
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
/// entity ::= module | class | fn
pub(super) fn parse_entity(&mut self) -> Option<Entity> { pub(super) fn parse_entity(&mut self) -> Option<Entity> {
use TokenKeyword::*; use TokenKeyword::*;
let token = self.peek_token(); let token = self.peek_token();
@@ -17,7 +18,7 @@ impl<'a> Parser<'a> {
match keyword { match keyword {
Module => Some(Entity::Module(self.parse_module()?)), Module => Some(Entity::Module(self.parse_module()?)),
Class => Some(Entity::Class(self.parse_class()?)), Class => Some(Entity::Class(self.parse_class()?)),
Fn => Some(Entity::Fn(self.parse_function()?)), Fn => Some(Entity::Fn(self.parse_fn()?)),
_ => { _ => {
self.error_expected_peek("entity"); self.error_expected_peek("entity");
None None
@@ -29,6 +30,7 @@ impl<'a> Parser<'a> {
} }
} }
/// module ::= "module" ident "{" { module | fn | static | class } "}"
fn parse_module(&mut self) -> Option<Module> { fn parse_module(&mut self) -> Option<Module> {
self.next_token(); self.next_token();
@@ -46,7 +48,7 @@ impl<'a> Parser<'a> {
if let TokenKind::Keyword(keyword) = &self.peek_token().kind { if let TokenKind::Keyword(keyword) = &self.peek_token().kind {
children.push(match keyword { children.push(match keyword {
Module => ModuleChildren::Module(self.parse_module()?), Module => ModuleChildren::Module(self.parse_module()?),
Fn => ModuleChildren::Fn(self.parse_function()?), Fn => ModuleChildren::Fn(self.parse_fn()?),
Static => ModuleChildren::Static(self.parse_static()?), Static => ModuleChildren::Static(self.parse_static()?),
Class => ModuleChildren::Class(self.parse_class()?), Class => ModuleChildren::Class(self.parse_class()?),
_ => { _ => {
@@ -65,6 +67,7 @@ impl<'a> Parser<'a> {
Some(Module { name, children }) Some(Module { name, children })
} }
/// class ::= "class" ident "{" { fn | static | let } "}"
fn parse_class(&mut self) -> Option<Class> { fn parse_class(&mut self) -> Option<Class> {
self.next_token(); self.next_token();
@@ -81,7 +84,7 @@ impl<'a> Parser<'a> {
self.trim_newlines(); self.trim_newlines();
if let TokenKind::Keyword(keyword) = &self.peek_token().kind { if let TokenKind::Keyword(keyword) = &self.peek_token().kind {
children.push(match keyword { children.push(match keyword {
Fn => ClassChildren::Fn(self.parse_function()?), Fn => ClassChildren::Fn(self.parse_fn()?),
Static => ClassChildren::Static(self.parse_static()?), Static => ClassChildren::Static(self.parse_static()?),
Let => ClassChildren::Let(self.parse_let()?), Let => ClassChildren::Let(self.parse_let()?),
_ => { _ => {
@@ -100,7 +103,9 @@ impl<'a> Parser<'a> {
Some(Class { name, children }) Some(Class { name, children })
} }
fn parse_function(&mut self) -> Option<Fn> { /// fn ::= "fn" ident "(" [ identWithTy { "," identWithTy } ] ")" [ ":" ty ]
/// "{" { statement } "}"
fn parse_fn(&mut self) -> Option<Fn> {
self.next_token(); self.next_token();
let name = self.parse_ident()?; let name = self.parse_ident()?;
@@ -115,7 +120,7 @@ impl<'a> Parser<'a> {
loop { loop {
if self.peek_token().kind == TokenKind::Identifier { if self.peek_token().kind == TokenKind::Identifier {
params.push(self.parse_ident_with_type()?); params.push(self.parse_ident_with_ty()?);
} }
if !self.skip_token(TokenKind::Symbol(TokenSymbol::Comma)) { if !self.skip_token(TokenKind::Symbol(TokenSymbol::Comma)) {
@@ -164,16 +169,7 @@ fn test_parse_entity() {
} }
} }
fn fn02 (): int { }
fn fn02(): int {
}
}"#, }"#,
); );
assert_eq!( assert_eq!(
@@ -190,7 +186,7 @@ fn test_parse_entity() {
children: vec![Statement::Static(Let { children: vec![Statement::Static(Let {
name: "let01".into(), name: "let01".into(),
ty: Ty::Int, ty: Ty::Int,
expr: Some(Expr::Int(4)) expr: Some(Expr::Literal(Literal::Int(4)))
})] })]
})] })]
}), }),

View File

@@ -1,109 +1,356 @@
use super::Parser; use super::Parser;
use crate::ast::*; use crate::ast::{
self,
*,
};
use crate::lexer::{ use crate::lexer::{
TokenDelimiter,
TokenKeyword, TokenKeyword,
TokenKind, TokenKind,
TokenLiteral, TokenLiteral,
TokenSymbol, TokenSymbol,
}; };
use std::rc::Rc;
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
pub(super) fn parse_statement(&mut self) -> Option<Statement> { /// exprIf ::= "if" expr block [ else (block | exprIf ) ]
use TokenKeyword::*; fn parse_expr_if(&mut self) -> Option<If> {
// skip "if"
match self.peek_token().kind {
TokenKind::Keyword(Static) => Some(Statement::Static(self.parse_static()?)),
TokenKind::Keyword(Let) => Some(Statement::Let(self.parse_let()?)),
_ => Some(Statement::Expr(self.parse_expr()?)),
}
}
pub(super) fn parse_static(&mut self) -> Option<Let> {
self.next_token();
if self.peek_token().kind != TokenKind::Keyword(TokenKeyword::Let) {
self.error_expected_peek("let");
return None;
}
self.parse_let()
}
pub(super) fn parse_let(&mut self) -> Option<Let> {
self.next_token(); self.next_token();
let (name, ty) = self.parse_ident_with_type()?; let cond = Box::new(self.parse_expr()?);
let expr; let then = self.parse_expr_block()?;
if self.skip_token(TokenKind::Symbol(TokenSymbol::Eq)) { if !self.skip_token(TokenKind::Keyword(TokenKeyword::Else)) {
expr = Some(self.parse_expr()?); return Some(If {
cond,
then,
or: None,
});
}
if self.peek_token().kind != TokenKind::Keyword(TokenKeyword::If) {
return Some(If {
cond,
then,
or: Some(Box::new(ElseType::Else(self.parse_expr_block()?))),
});
}
Some(If {
cond,
then,
or: Some(Box::new(ElseType::If(self.parse_expr_if()?))),
})
}
/// exprBlock ::= "{" { statement } "}"
fn parse_expr_block(&mut self) -> Option<Vec<Statement>> {
let mut statements = vec![];
// skip {
self.next_token();
loop {
self.trim_newlines(); self.trim_newlines();
} else if self.skip_token(TokenKind::Newline) { if self.skip_token(TokenKind::Delimiter(TokenDelimiter::BraceClose)) {
expr = None; break;
} else { }
self.error_expected_peek("= or newline"); statements.push(self.parse_statement()?);
}
Some(statements)
}
/// exprLoop ::= "loop" exprBlock
fn parse_expr_loop(&mut self) -> Option<Vec<Statement>> {
self.next_token();
if self.peek_token().kind != TokenKind::Delimiter(TokenDelimiter::BraceOpen) {
self.error_expected_peek("{");
return None; return None;
} }
Some(Let { name, ty, expr }) self.parse_expr_block()
} }
fn parse_expr(&mut self) -> Option<Expr> { /// exprAtom ::= ( "(" expr ")" ) | ident | int | float | char | exprBlock | exprLoop | exprIf
fn parse_expr_atom(&mut self) -> Option<Expr> {
use ast::Literal::*;
use TokenKind::*; use TokenKind::*;
match self.peek_token().kind { // TODO: check lvalue validity in the analysis phase
Literal(TokenLiteral::Int) => Some(Expr::Int(self.parse_int()?)), Some(match self.peek_token().kind {
Literal(TokenLiteral::Float) => Some(Expr::Float(self.parse_float()?)), Delimiter(TokenDelimiter::ParenOpen) => {
Literal(TokenLiteral::Char) => Some(Expr::Char(self.parse_char()?)), self.next_token(); // skip (
_ => {
self.error_expected_peek("expression"); let expr = self.parse_expr()?;
None
} if !self.skip_token(TokenKind::Delimiter(TokenDelimiter::ParenClose)) {
} self.error_expected_peek(")");
} return None;
} }
#[test] expr
fn test_parse_let() { }
let mut parser = Parser::new( Identifier => {
r#"static let test01: int = 4 let token = self.next_token();
let test02: char = '6' Expr::Identifier(Rc::clone(&token.val))
static let test03: float }
let test04 = 9"#, Literal(TokenLiteral::Int) => Expr::Literal(Int(self.parse_int()?)),
); Literal(TokenLiteral::Float) => Expr::Literal(Float(self.parse_float()?)),
assert_eq!( Literal(TokenLiteral::Char) => Expr::Literal(Char(self.parse_char()?)),
parser.parse_static(), Delimiter(TokenDelimiter::BraceOpen) => Expr::Block(self.parse_expr_block()?),
Some(Let { Keyword(TokenKeyword::Loop) => Expr::Loop(self.parse_expr_loop()?),
name: "test01".into(), Keyword(TokenKeyword::If) => Expr::If(self.parse_expr_if()?),
ty: Ty::Int, _ => {
expr: Some(Expr::Int(4)) self.error_expected_peek("expression");
return None;
}
}) })
); }
assert_eq!(
parser.parse_let(), /// exprUnary ::= [ unaryOp ] exprAtom
Some(Let { /// unaryOp ::= "+" | "-" | "~"
name: "test02".into(), fn parse_expr_unary(&mut self) -> Option<Expr> {
ty: Ty::Char, use TokenSymbol::*;
expr: Some(Expr::Char('6')) Some(match self.peek_token().kind {
TokenKind::Symbol(symbol @ (Minus | Plus | Tilde)) => {
self.next_token();
Expr::Op(symbol, Box::new(self.parse_expr_atom()?), None)
}
_ => self.parse_expr_atom()?,
}) })
); }
assert_eq!(
parser.parse_static(), /// exprArithmeticMul ::= exprUnary [ arithmeticMulOp exprArithmeticMul ]
Some(Let { /// arithmeticMulOp ::= "*" | "/" | "%"
name: "test03".into(), fn parse_expr_arithmetic_mul(&mut self) -> Option<Expr> {
ty: Ty::Float, use TokenSymbol::*;
expr: None let lhs = self.parse_expr_unary()?;
Some(match self.peek_token().kind {
TokenKind::Symbol(symbol @ (Star | Slash | Percent)) => {
self.next_token();
Expr::Op(
symbol,
Box::new(lhs),
Some(Box::new(self.parse_expr_arithmetic_mul()?)),
)
}
_ => lhs,
}) })
); }
assert_eq!(parser.parse_let(), None);
/// exprArithmeticAdd ::= exprArithmeticMul [ arithmeticAddOp exprArithmeticAdd ]
/// arithmeticAddOp ::= "+" | "-"
fn parse_expr_arithmetic_add(&mut self) -> Option<Expr> {
use TokenSymbol::*;
let lhs = self.parse_expr_arithmetic_mul()?;
Some(match self.peek_token().kind {
TokenKind::Symbol(symbol @ (Plus | Minus)) => {
self.next_token();
Expr::Op(
symbol,
Box::new(lhs),
Some(Box::new(self.parse_expr_arithmetic_add()?)),
)
}
_ => lhs,
})
}
/// exprBitwiseShift ::= exprArithmeticAdd [ bitwiseShiftOp exprBitwiseShift ]
/// bitwiseShiftOp ::= "<<" | ">>"
fn parse_expr_bitwise_shift(&mut self) -> Option<Expr> {
use TokenSymbol::*;
let lhs = self.parse_expr_arithmetic_add()?;
Some(match self.peek_token().kind {
TokenKind::Symbol(symbol @ (Shl | Shr)) => {
self.next_token();
Expr::Op(
symbol,
Box::new(lhs),
Some(Box::new(self.parse_expr_bitwise_shift()?)),
)
}
_ => lhs,
})
}
/// exprBitwiseAnd ::= exprBitwiseShift [ "&" exprBitwiseAnd ]
fn parse_expr_bitwise_and(&mut self) -> Option<Expr> {
let lhs = self.parse_expr_bitwise_shift()?;
let symbol = TokenSymbol::And;
if !self.skip_token(TokenKind::Symbol(symbol)) {
return Some(lhs);
}
Some(Expr::Op(
symbol,
Box::new(lhs),
Some(Box::new(self.parse_expr_bitwise_and()?)),
))
}
/// exprBitwiseXor ::= exprBitwiseAnd [ "^" exprBitwiseXor ]
fn parse_expr_bitwise_xor(&mut self) -> Option<Expr> {
let lhs = self.parse_expr_bitwise_and()?;
let symbol = TokenSymbol::Caret;
if !self.skip_token(TokenKind::Symbol(symbol)) {
return Some(lhs);
}
Some(Expr::Op(
symbol,
Box::new(lhs),
Some(Box::new(self.parse_expr_bitwise_xor()?)),
))
}
/// exprBiwiseOr ::= exprBitwiseXor [ "|" exprBitwiseOr ]
fn parse_expr_bitwise_or(&mut self) -> Option<Expr> {
let lhs = self.parse_expr_bitwise_xor()?;
let symbol = TokenSymbol::Or;
if !self.skip_token(TokenKind::Symbol(symbol)) {
return Some(lhs);
}
Some(Expr::Op(
symbol,
Box::new(lhs),
Some(Box::new(self.parse_expr_bitwise_or()?)),
))
}
/// exprAssign ::= exprBitwiseOr [ relationalOp exprRelational ]
/// relationalOp ::= ">" | "<" | ">=" | "<=" | "==" | "!="
fn parse_expr_relational(&mut self) -> Option<Expr> {
use TokenSymbol::*;
let lhs = self.parse_expr_bitwise_or()?;
Some(match self.peek_token().kind {
TokenKind::Symbol(symbol @ (Gt | Lt | GtEq | LtEq | EqEq | Ne)) => {
self.next_token();
Expr::Op(
symbol,
Box::new(lhs),
Some(Box::new(self.parse_expr_relational()?)),
)
}
_ => lhs,
})
}
/// exprLogicalAnd ::= exprLogicalRelational [ "&&" exprLogicalAnd ]
fn parse_expr_logical_and(&mut self) -> Option<Expr> {
let lhs = self.parse_expr_relational()?;
let symbol = TokenSymbol::AndAnd;
if !self.skip_token(TokenKind::Symbol(symbol)) {
return Some(lhs);
}
Some(Expr::Op(
symbol,
Box::new(lhs),
Some(Box::new(self.parse_expr_logical_and()?)),
))
}
/// exprLogicalOr ::= exprLogicalAnd [ "||" exprLogicalOr ]
fn parse_expr_logical_or(&mut self) -> Option<Expr> {
let lhs = self.parse_expr_logical_and()?;
let symbol = TokenSymbol::OrOr;
if !self.skip_token(TokenKind::Symbol(symbol)) {
return Some(lhs);
}
Some(Expr::Op(
symbol,
Box::new(lhs),
Some(Box::new(self.parse_expr_logical_or()?)),
))
}
/// exprAssign ::= exprLogicalOr [ assignOp exprAssign ]
/// assignOp ::= "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "^=" | "<<=" | ">>=" | "&=" | "|="
fn parse_expr_assign(&mut self) -> Option<Expr> {
use TokenSymbol::*;
let lhs = self.parse_expr_logical_or()?;
Some(match self.peek_token().kind {
TokenKind::Symbol(
symbol @ (Eq | PlusEq | MinusEq | StarEq | SlashEq | PercentEq | CaretEq | ShlEq
| ShrEq | AndEq | OrEq),
) => {
self.next_token();
Expr::Op(
symbol,
Box::new(lhs),
Some(Box::new(self.parse_expr_assign()?)),
)
}
_ => lhs,
})
}
/// exprControl ::= "continue" | "break" | "return" [ exprControl ] | exprAssign
fn parse_expr_control(&mut self) -> Option<Expr> {
use TokenKeyword::*;
Some(match self.peek_token().kind {
TokenKind::Keyword(Continue) => {
self.next_token();
Expr::Continue
}
TokenKind::Keyword(Break) => {
self.next_token();
Expr::Break
}
TokenKind::Keyword(Return) => {
self.next_token();
Expr::Return(self.parse_expr_control().map(Box::new))
}
_ => self.parse_expr_assign()?,
})
}
/// entrypoint for expression parsing using recursive descent parsing
///
/// <https://en.wikipedia.org/wiki/Recursive_descent_parser>
/// expr ::= exprControl
fn parse_expr(&mut self) -> Option<Expr> {
self.parse_expr_control()
}
pub(super) fn parse_expr_ln(&mut self) -> Option<Expr> {
let expr = self.parse_expr();
if !self.skip_token(TokenKind::Newline) {
self.error_expected_peek("newline");
return None;
}
expr
}
} }
#[test] #[test]
fn test_parse_expr_literals() { fn test_parse_expr_literals() {
use Literal::*;
let mut parser = Parser::new("4524 3123.15e4 9e2 9083482.429455 'c' 3331.13.3"); let mut parser = Parser::new("4524 3123.15e4 9e2 9083482.429455 'c' 3331.13.3");
assert_eq!(parser.parse_expr(), Some(Expr::Int(4524))); assert_eq!(parser.parse_expr(), Some(Expr::Literal(Int(4524))));
assert_eq!(parser.parse_expr(), Some(Expr::Float(3123.15e4))); assert_eq!(parser.parse_expr(), Some(Expr::Literal(Float(3123.15e4))));
assert_eq!(parser.parse_expr(), Some(Expr::Float(9e2))); assert_eq!(parser.parse_expr(), Some(Expr::Literal(Float(9e2))));
assert_eq!(parser.parse_expr(), Some(Expr::Float(9083482.429455))); assert_eq!(
assert_eq!(parser.parse_expr(), Some(Expr::Char('c'))); parser.parse_expr(),
Some(Expr::Literal(Float(9083482.429455)))
);
assert_eq!(parser.parse_expr(), Some(Expr::Literal(Char('c'))));
assert_eq!(parser.parse_expr(), None); assert_eq!(parser.parse_expr(), None);
} }

View File

@@ -1,10 +1,12 @@
use super::Parser; use super::Parser;
use crate::lexer::{ use crate::lexer::{
TokenKind, TokenKind,
TokenLiteral,
TokenSymbol, TokenSymbol,
}; };
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
/// int ::= digit { digit }
pub(super) fn parse_int(&mut self) -> Option<i32> { pub(super) fn parse_int(&mut self) -> Option<i32> {
let val = self.next_token().val; let val = self.next_token().val;
let mut integer: i32 = 0; let mut integer: i32 = 0;
@@ -36,10 +38,13 @@ impl<'a> Parser<'a> {
} }
} }
} }
Some(integer) Some(integer)
} }
// didnt use parse() because i wanted to do this myself for some reason
/// f32 can be NaN and inf as well /// f32 can be NaN and inf as well
/// float ::= int [ "." { digit } ] [ "e" { digit } ]
pub(super) fn parse_float(&mut self) -> Option<f32> { pub(super) fn parse_float(&mut self) -> Option<f32> {
let token = self.next_token(); let token = self.next_token();
let mut chars = token.val.chars(); let mut chars = token.val.chars();
@@ -68,6 +73,10 @@ impl<'a> Parser<'a> {
_ => s = 1, _ => s = 1,
} }
if self.peek_token().kind != TokenKind::Literal(TokenLiteral::Int) {
break;
}
exp = self.parse_int()? * s; exp = self.parse_int()? * s;
break; break;
} }
@@ -93,6 +102,7 @@ impl<'a> Parser<'a> {
Some(float) Some(float)
} }
/// char ::= "'" letter "'"
pub(super) fn parse_char(&mut self) -> Option<char> { pub(super) fn parse_char(&mut self) -> Option<char> {
// the lexer ensures that the 0th and 2nd characters are both ' // the lexer ensures that the 0th and 2nd characters are both '
self.next_token().val.chars().nth(1) self.next_token().val.chars().nth(1)

View File

@@ -5,6 +5,7 @@
mod entity; mod entity;
mod expr; mod expr;
mod literal; mod literal;
mod statement;
use crate::ast::{ use crate::ast::{
Parent, Parent,
@@ -60,6 +61,7 @@ impl<'a> Parser<'a> {
return self.lexer.peek_token(); return self.lexer.peek_token();
} }
/// newline ::= "\n"
fn trim_newlines(&mut self) { fn trim_newlines(&mut self) {
while self.peek_token().kind == TokenKind::Newline { while self.peek_token().kind == TokenKind::Newline {
self.next_token(); self.next_token();
@@ -75,6 +77,7 @@ impl<'a> Parser<'a> {
false false
} }
/// ty ::= "int" | "float" | "char"
fn parse_ty(&mut self) -> Option<Ty> { fn parse_ty(&mut self) -> Option<Ty> {
let ty: Ty; let ty: Ty;
@@ -96,6 +99,7 @@ impl<'a> Parser<'a> {
Some(ty) Some(ty)
} }
/// ident ::= ( letter | "_" ) { letter | digit | "_" }
fn parse_ident(&mut self) -> Option<Rc<str>> { fn parse_ident(&mut self) -> Option<Rc<str>> {
if self.peek_token().kind != TokenKind::Identifier { if self.peek_token().kind != TokenKind::Identifier {
self.error_expected_peek("identifier"); self.error_expected_peek("identifier");
@@ -105,7 +109,8 @@ impl<'a> Parser<'a> {
Some(Rc::clone(&self.next_token().val)) Some(Rc::clone(&self.next_token().val))
} }
fn parse_ident_with_type(&mut self) -> Option<(Rc<str>, Ty)> { /// identWithTy ::= letter ":" ty
fn parse_ident_with_ty(&mut self) -> Option<(Rc<str>, Ty)> {
let ident = self.parse_ident()?; let ident = self.parse_ident()?;
if !self.skip_token(TokenKind::Symbol(TokenSymbol::Colon)) { if !self.skip_token(TokenKind::Symbol(TokenSymbol::Colon)) {
@@ -118,6 +123,7 @@ impl<'a> Parser<'a> {
/// Returns an [`Entity`] vector after parsing /// Returns an [`Entity`] vector after parsing
/// ///
/// parent ::= { entity }
/// [`Entity`]: crate::ast::Entity /// [`Entity`]: crate::ast::Entity
pub fn parse(&mut self) -> Option<Parent> { pub fn parse(&mut self) -> Option<Parent> {
let mut parent = vec![]; let mut parent = vec![];

87
src/parser/statement.rs Normal file
View File

@@ -0,0 +1,87 @@
use super::Parser;
use crate::ast::*;
use crate::lexer::{
TokenKeyword,
TokenKind,
TokenSymbol,
};
impl<'a> Parser<'a> {
/// statement ::= static | let | expr
pub(super) fn parse_statement(&mut self) -> Option<Statement> {
use TokenKeyword::*;
Some(match self.peek_token().kind {
TokenKind::Keyword(Static) => Statement::Static(self.parse_static()?),
TokenKind::Keyword(Let) => Statement::Let(self.parse_let()?),
_ => Statement::Expr(self.parse_expr_ln()?),
})
}
/// static ::="static" let
pub(super) fn parse_static(&mut self) -> Option<Let> {
self.next_token();
if self.peek_token().kind != TokenKind::Keyword(TokenKeyword::Let) {
self.error_expected_peek("let");
return None;
}
self.parse_let()
}
/// let ::= "let" identWithTy "=" expr
pub(super) fn parse_let(&mut self) -> Option<Let> {
self.next_token();
let (name, ty) = self.parse_ident_with_ty()?;
let expr = if self.skip_token(TokenKind::Symbol(TokenSymbol::Eq)) {
self.parse_expr_ln()
} else if self.skip_token(TokenKind::Newline) {
None
} else {
self.error_expected_peek("= or newline");
return None;
};
Some(Let { name, ty, expr })
}
}
#[test]
fn test_parse_let() {
use Literal::*;
let mut parser = Parser::new(
r#"static let test01: int = 4
let test02: char = '6'
static let test03: float
let test04 = 9"#,
);
assert_eq!(
parser.parse_static(),
Some(Let {
name: "test01".into(),
ty: Ty::Int,
expr: Some(Expr::Literal(Int(4)))
})
);
assert_eq!(
parser.parse_let(),
Some(Let {
name: "test02".into(),
ty: Ty::Char,
expr: Some(Expr::Literal(Char('6')))
})
);
assert_eq!(
parser.parse_static(),
Some(Let {
name: "test03".into(),
ty: Ty::Float,
expr: None
})
);
assert_eq!(parser.parse_let(), None);
}