Skip to content

Commit

Permalink
begin implementing Parsable for some basic AST nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
andogq committed Aug 30, 2024
1 parent dfb3309 commit 061f396
Show file tree
Hide file tree
Showing 9 changed files with 523 additions and 87 deletions.
129 changes: 129 additions & 0 deletions src/hir/expression/array.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 @@ -8,6 +10,59 @@ ast_node! {
}
}

impl<M: AstMetadata> Parsable for Array<M> {
fn register(parser: &mut Parser) {
assert!(
parser.register_prefix(Token::LeftSquare, |parser, compiler, lexer| {
// Parse opening square bracket
let span_start = match lexer.next_spanned().unwrap() {
(Token::LeftSquare, span) => span.start,
(token, _) => {
return Err(ParseError::ExpectedToken {
expected: Box::new(Token::LeftSquare),
found: Box::new(token),
reason: "array literal must start with square brace".to_string(),
});
}
};

// Parse each of the items, deliminated by a comma
let mut init = Vec::new();
let mut expect_item = true;
let span_end = loop {
dbg!("loop");
match (lexer.peek_token().unwrap(), expect_item) {
(Token::Comma, false) => {
expect_item = true;
lexer.next_token();
}
(Token::RightSquare, _) => {
break lexer.next_spanned().unwrap().1.end;
}
(_, true) => {
init.push(parser.parse(compiler, lexer, Precedence::Lowest)?);
expect_item = false;
}
(token, _) => {
return Err(ParseError::ExpectedToken {
expected: Box::new(Token::RightSquare),
found: Box::new(token.clone()),
reason: "expected a comma or closing brace".to_string(),
});
}
}
};

Ok(Expression::Array(Array {
init,
span: span_start..span_end,
ty_info: None,
}))
})
);
}
}

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

Expand Down Expand Up @@ -42,3 +97,77 @@ impl SolveType for Array<UntypedAstMetadata> {
})
}
}

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

mod parse {
use crate::stage::parse::{Lexer, Precedence};

use super::*;

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

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

// Use integer parser for testing
Integer::<UntypedAstMetadata>::register(&mut parser);

parser
}

#[rstest]
#[case::empty("[]", 0)]
#[case::single("[1]", 1)]
#[case::single_trailing("[1,]", 1)]
#[case::double("[1, 2]", 2)]
#[case::double_trailing("[1, 2,]", 2)]
#[case::triple("[1, 2, 3]", 3)]
#[case::triple_trailing("[1, 2, 3,]", 3)]
fn flat(parser: Parser, #[case] source: &str, #[case] items: usize) {
let array = parser
.parse(
&mut Compiler::default(),
&mut Lexer::from(source),
Precedence::Lowest,
)
.unwrap();

assert_array_len(&array, items);
}

#[rstest]
fn nested(parser: Parser) {
let array = parser
.parse(
&mut Compiler::default(),
&mut Lexer::from("[[1,], [1, 2,], [1, 2, 3,],]"),
Precedence::Lowest,
)
.unwrap();

let array = assert_array_len(&array, 3);

assert_array_len(&array.init[0], 1);
assert_array_len(&array.init[1], 2);
assert_array_len(&array.init[2], 3);
}

fn assert_array_len<M: AstMetadata>(
expression: &Expression<M>,
length: usize,
) -> &Array<M> {
let Expression::Array(array) = expression else {
panic!("expected to parse array");
};

assert_eq!(array.init.len(), length);

array
}
}
}
97 changes: 97 additions & 0 deletions src/hir/expression/assign.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 @@ -9,6 +11,44 @@ ast_node! {
}
}

impl<M: AstMetadata> Parsable for Assign<M> {
fn register(parser: &mut Parser) {
assert!(
parser.register_infix(Token::Eq, |parser, compiler, lexer, left| {
let (binding, binding_span) = match left {
Expression::Ident(Ident { binding, span, .. }) => (binding, span),
lhs => {
return Err(ParseError::InvalidInfixLhs {
found: Box::new(lhs),
reason: "assign must start with ident".to_string(),
});
}
};

match lexer.next_token().unwrap() {
Token::Eq => (),
token => {
return Err(ParseError::ExpectedToken {
expected: Box::new(Token::Eq),
found: Box::new(token),
reason: "equals sign following binding for assign".to_string(),
});
}
}

let value = parser.parse(compiler, lexer, Precedence::Lowest)?;

Ok(Expression::Assign(Assign {
span: binding_span.start..value.span().end,
binding,
value: Box::new(value),
ty_info: None,
}))
})
);
}
}

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

Expand Down Expand Up @@ -41,3 +81,60 @@ impl SolveType for Assign<UntypedAstMetadata> {
})
}
}

#[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();

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

// Register additional parsers for testing
Integer::<UntypedAstMetadata>::register(&mut parser);
Ident::<UntypedAstMetadata>::register(&mut parser);

parser
}

#[rstest]
#[case::valid_integer_rhs("myident", "1")]
#[case::valid_ident_rhs("myident", "otherident")]
fn success(parser: Parser, #[case] lhs: &str, #[case] rhs: &str) {
let mut compiler = Compiler::default();

let assign = parser
.parse(
&mut compiler,
&mut Lexer::from(format!("{lhs} = {rhs}").as_str()),
Precedence::Lowest,
)
.unwrap();

let Expression::Assign(assign) = dbg!(assign) else {
panic!("expected to parse assignment")
};

assert_eq!(lhs, compiler.symbols.resolve(assign.binding).unwrap());
}

#[rstest]
fn invalid(parser: Parser) {
let result = parser.parse(
&mut Compiler::default(),
&mut Lexer::from("1 = otherident"),
Precedence::Lowest,
);

assert!(matches!(result, Err(ParseError::InvalidInfixLhs { .. })));
}
}
}
113 changes: 92 additions & 21 deletions src/hir/expression/boolean.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::stage::parse::{Lexer, ParseError};

use super::*;

ast_node! {
Expand All @@ -8,6 +10,41 @@ ast_node! {
}
}

impl<M: AstMetadata> Parsable for Boolean<M> {
fn register(parser: &mut Parser) {
fn parse(lexer: &mut Lexer) -> Result<Expression<UntypedAstMetadata>, ParseError> {
let (token, span) = lexer.next_spanned().unwrap();

let value = match token {
Token::True => true,
Token::False => false,
token => {
return Err(ParseError::ExpectedToken {
expected: Box::new(Token::True),
found: Box::new(token),
reason: "expected boolean".to_string(),
});
}
};

Ok(Expression::Boolean(Boolean {
value,
span,
ty_info: None,
}))
}

assert!(
parser.register_prefix(Token::True, |_, _, lexer| parse(lexer)),
"successfully register parser for `true` token"
);
assert!(
parser.register_prefix(Token::False, |_, _, lexer| parse(lexer)),
"successfully register parser for `false` token"
);
}
}

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

Expand All @@ -28,30 +65,64 @@ impl SolveType for Boolean<UntypedAstMetadata> {
}

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

#[test]
fn boolean_infer() {
assert_eq!(
Boolean::new(false, Span::default(), Default::default())
.solve(&mut Compiler::default(), &mut Scope::new())
.unwrap()
.ty_info
.ty,
Ty::Boolean
);
use rstest::*;

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

#[rstest]
#[case::t_true("true", true)]
#[case::t_false("false", false)]
fn success(#[case] source: &str, #[case] value: bool) {
let mut parser = Parser::new();

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

let boolean = parser
.parse(
&mut Compiler::default(),
&mut Lexer::from(source),
Precedence::Lowest,
)
.unwrap();

let Expression::Boolean(boolean) = boolean else {
panic!("expected boolean to be returned");
};

assert_eq!(boolean.value, value);
}
}

#[test]
fn boolean_return() {
assert_eq!(
Boolean::new(false, Span::default(), Default::default())
.solve(&mut Compiler::default(), &mut Scope::new())
.unwrap()
.ty_info
.return_ty,
None,
);
mod ty {
use super::*;

#[test]
fn boolean_infer() {
assert_eq!(
Boolean::new(false, Span::default(), Default::default())
.solve(&mut Compiler::default(), &mut Scope::new())
.unwrap()
.ty_info
.ty,
Ty::Boolean
);
}

#[test]
fn boolean_return() {
assert_eq!(
Boolean::new(false, Span::default(), Default::default())
.solve(&mut Compiler::default(), &mut Scope::new())
.unwrap()
.ty_info
.return_ty,
None,
);
}
}
}
Loading

0 comments on commit 061f396

Please sign in to comment.