Skip to content

Commit

Permalink
finalise parsing for expression and statements
Browse files Browse the repository at this point in the history
  • Loading branch information
andogq committed Sep 2, 2024
1 parent 4e528db commit 2323d18
Show file tree
Hide file tree
Showing 11 changed files with 820 additions and 75 deletions.
109 changes: 109 additions & 0 deletions src/hir/expression/block.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,66 @@
use crate::stage::parse::{ParseError, Precedence};

use super::*;

ast_node! {
Block<M> {
statements: Vec<Statement<M>>,
terminated: bool,
span,
ty_info,
}
}

impl<M: AstMetadata> Parsable for Block<M> {
fn register(parser: &mut Parser) {
assert!(parser.register_prefix::<Expression<UntypedAstMetadata>>(
Token::LeftBrace,
|parser, compiler, lexer| {
// Parse opening bracket
let start_span = match lexer.next_spanned().ok_or(ParseError::UnexpectedEOF)? {
(Token::LeftBrace, span) => span,
(token, _) => {
return Err(ParseError::ExpectedToken {
expected: Box::new(Token::LeftBrace),
found: Box::new(token),
reason: "block must start with opening brace".to_string(),
});
}
};

// Parse statements
let (statements, terminated) = parser
.parse_delimited::<Statement<UntypedAstMetadata>, Precedence>(
compiler,
lexer,
Token::SemiColon,
);

dbg!(&statements, terminated);

// Parse ending bracket
let end_span = match lexer.next_spanned().ok_or(ParseError::UnexpectedEOF)? {
(Token::RightBrace, span) => span,
(token, _) => {
return Err(ParseError::ExpectedToken {
expected: Box::new(Token::RightBrace),
found: Box::new(token),
reason: "block must end with closing brace".to_string(),
});
}
};

Ok(Expression::Block(Block {
statements,
terminated,
span: start_span.start..end_span.end,
ty_info: None,
}))
}
));
}
}

impl SolveType for Block<UntypedAstMetadata> {
type State = Scope;

Expand Down Expand Up @@ -55,8 +108,64 @@ impl SolveType for Block<UntypedAstMetadata> {

Ok(Block {
span: self.span,
terminated: self.terminated,
statements,
ty_info,
})
}
}

#[cfg(test)]
mod test {
use super::*;
use rstest::*;

mod parse {
use crate::stage::parse::Lexer;

use super::*;

#[fixture]
fn parser() -> Parser {
let mut parser = Parser::new();

Block::<UntypedAstMetadata>::register(&mut parser);

// Helpers
ExpressionStatement::<UntypedAstMetadata>::register(&mut parser);
Boolean::<UntypedAstMetadata>::register(&mut parser);

parser
}

#[rstest]
#[case::empty("{ }", false, 0)]
#[case::single_terminated("{ true; }", true, 1)]
#[case::single_unterminated("{ true }", false, 1)]
#[case::double_terminated("{ true; true; }", true, 2)]
#[case::double_unterminated("{ true; true }", false, 2)]
#[case::triple_terminated("{ true; true; true; }", true, 3)]
#[case::triple_unterminated("{ true; true; true }", false, 3)]
fn success(
parser: Parser,
#[case] source: &str,
#[case] terminated: bool,
#[case] count: usize,
) {
let b: Expression<UntypedAstMetadata> = parser
.parse(
&mut Compiler::default(),
&mut Lexer::from(source),
Precedence::Lowest,
)
.unwrap();

let Expression::Block(b) = b else {
panic!("expected to parse block");
};

assert_eq!(b.terminated, terminated);
assert_eq!(b.statements.len(), count);
}
}
}
187 changes: 187 additions & 0 deletions src/hir/expression/if_else.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::stage::parse::{ParseError, Precedence};

use super::*;

ast_node! {
Expand All @@ -10,6 +12,62 @@ ast_node! {
}
}

impl<M: AstMetadata> Parsable for If<M> {
fn register(parser: &mut Parser) {
assert!(
parser.register_prefix(Token::If, |parser, compiler, lexer| {
let span_start = match lexer.next_spanned().ok_or(ParseError::UnexpectedEOF)? {
(Token::If, span) => span,
(token, _) => {
return Err(ParseError::ExpectedToken {
expected: Box::new(Token::If),
found: Box::new(token),
reason: "expected if statement".to_string(),
});
}
};

// Parse condition
let condition = parser.parse(compiler, lexer, Precedence::Lowest)?;

// Parse out success block
let Expression::<UntypedAstMetadata>::Block(success) =
parser.parse(compiler, lexer, Precedence::Lowest)?
else {
return Err(ParseError::ExpectedBlock);
};

let otherwise = matches!(lexer.peek_token(), Some(Token::Else))
.then(|| {
lexer.next_token().unwrap();

let Expression::<UntypedAstMetadata>::Block(otherwise) =
parser.parse(compiler, lexer, Precedence::Lowest)?
else {
return Err(ParseError::ExpectedBlock);
};

Ok(otherwise)
})
.transpose()?;

let span_end = otherwise
.as_ref()
.map(|otherwise| otherwise.span.clone())
.unwrap_or(success.span.clone());

Ok(Expression::If(If {
condition: Box::new(condition),
success,
otherwise,
span: span_start.start..span_end.end,
ty_info: None,
}))
})
);
}
}

impl SolveType for If<UntypedAstMetadata> {
type State = Scope;

Expand Down Expand Up @@ -59,3 +117,132 @@ impl SolveType for If<UntypedAstMetadata> {
})
}
}

#[cfg(test)]
mod test {
use super::*;
use rstest::*;

mod parse {
use super::*;
use crate::stage::parse::Lexer;

#[fixture]
fn parser() -> Parser {
let mut parser = Parser::new();

If::<UntypedAstMetadata>::register(&mut parser);

// Register helper parser for tests
ExpressionStatement::<UntypedAstMetadata>::register(&mut parser);
Block::<UntypedAstMetadata>::register(&mut parser);
Integer::<UntypedAstMetadata>::register(&mut parser);

parser
}

fn is_integer(expression: Expression<UntypedAstMetadata>) -> bool {
matches!(expression, Expression::Integer(_))
}

fn is_integer_block(block: Block<UntypedAstMetadata>) -> bool {
matches!(
block.statements[0],
Statement::ExpressionStatement(ExpressionStatement {
expression: Expression::Integer(_),
..
})
)
}

#[rstest]
#[case::normal_if("if 123 { 1 }", is_integer, is_integer_block, None)]
#[case::normal_if_else(
"if 123 { 1 } else { 1 }",
is_integer,
is_integer_block,
// Weird type has to be provided bc it's infered too tightly
Some::<fn (Block<UntypedAstMetadata>) -> bool>(is_integer_block)
)]
#[case::nested_if_if("if 123 { if 456 { 1 } }", is_integer, |block: Block<UntypedAstMetadata>| {
matches!(
block.statements[0],
Statement::ExpressionStatement(ExpressionStatement {
expression: Expression::If(If {
otherwise: None,
..
}),
..
})
)
}, None)]
#[case::nested_if_if_else("if 123 { if 456 { 1 } else { 2 } }", is_integer, |block: Block<UntypedAstMetadata>| {
matches!(
block.statements[0],
Statement::ExpressionStatement(ExpressionStatement {
expression: Expression::If(If {
otherwise: Some(_),
..
}),
..
})
)
}, None)]
#[case::nested_if_if_else_else("if 123 { if 456 { 1 } else { 2 } } else { 3 }", is_integer, |block: Block<UntypedAstMetadata>| {
matches!(
block.statements[0],
Statement::ExpressionStatement(ExpressionStatement {
expression: Expression::If(If {
otherwise: Some(_),
..
}),
..
})
)
},
Some::<fn (Block<UntypedAstMetadata>) -> bool>(is_integer_block))]
fn success(
parser: Parser,
#[case] source: &str,
#[case] condition_test: fn(Expression<UntypedAstMetadata>) -> bool,
#[case] success_test: fn(Block<UntypedAstMetadata>) -> bool,
#[case] otherwise_test: Option<fn(Block<UntypedAstMetadata>) -> bool>,
) {
let eif = parser
.parse(
&mut Compiler::default(),
&mut Lexer::from(source),
Precedence::Lowest,
)
.unwrap();

let Expression::If(eif) = eif else {
panic!("expected to parse if condition");
};

assert!(condition_test(*eif.condition));
assert!(success_test(eif.success));

match (eif.otherwise, otherwise_test) {
(Some(otherwise), Some(otherwise_test)) => assert!(otherwise_test(otherwise)),
(Some(_), None) => panic!("otherwise branch encountered with no test for it"),
(None, Some(_)) => panic!("otherwise branch expected, but could not be parsed"),
(None, None) => {}
};
}

#[rstest]
#[case::missing_block("if 1")]
#[case::expression_after_condition("if 1 1")]
#[case::if_else_immediate("if else { 1 }")]
fn fail(parser: Parser, #[case] source: &str) {
assert!(parser
.parse::<Expression<UntypedAstMetadata>, _>(
&mut Compiler::default(),
&mut Lexer::from(source),
Precedence::Lowest
)
.is_err())
}
}
}
Loading

0 comments on commit 2323d18

Please sign in to comment.