Compare commits

...

2 Commits

Author SHA1 Message Date
mars 698c914633 Improve parser error handling 2022-02-28 14:36:24 -07:00
mars 04c0dc35a6 Refactor unit tests 2022-02-28 13:33:32 -07:00
6 changed files with 72 additions and 43 deletions

View File

@ -2,7 +2,7 @@ use logos::Logos;
use std::fmt::{Display, Formatter, Result as FmtResult};
#[rustfmt::skip]
#[derive(Logos, Debug, PartialEq)]
#[derive(Logos, Clone, Copy, Debug, PartialEq)]
pub enum Token {
// keywords
#[token("struct")] Struct,
@ -78,10 +78,10 @@ impl<'a> Display for TokenInfo<'a> {
let token_len = self.token_end - self.token_start;
if token_len > 1 {
write!(f, "{}^", "-".repeat(token_len - 2))?;
write!(f, "{}^\n", "-".repeat(token_len - 2))
} else {
write!(f, "\n")
}
Ok(())
}
}
@ -113,7 +113,27 @@ impl<'a> Lexer<'a> {
}
}
pub fn get_info(&self) -> TokenInfo<'a> {
pub fn eat_expect_id(&mut self) -> &'a str {
let tok = self.next();
if let Some(Token::Identifier) = tok {
self.slice()
} else {
self.panic_message("Expected identifier");
}
}
pub fn eat_expect(&mut self, expected: Token) {
let tok = self.next();
if tok != Some(expected) {
self.panic_message(&format!("Expected {:?}, got {:?}", expected, tok));
}
}
pub fn panic_message(&self, message: &str) -> ! {
panic!("{}\n{}", message, self.info());
}
pub fn info(&self) -> TokenInfo<'a> {
let from_start = &self.inner.source()[self.line_start..];
let line_end = from_start.find("\n").unwrap_or(from_start.len());
let line = &from_start[..line_end];

View File

@ -1,6 +1,6 @@
use console::Style;
pub mod ast;
pub mod parse;
pub mod lexer;
use lexer::Token;
@ -42,7 +42,7 @@ mod tests {
#[test]
fn lex_file() {
let source = include_str!("ast_fn.fae");
let source = include_str!("test/example.fae");
let theme = ColorTheme::new();
let mut lex = lexer::Lexer::new(source);
@ -56,7 +56,7 @@ mod tests {
// TODO use spans to color-code instead of raw tokens, to show original whitespace
/*#[test]
fn color_file() {
let source = include_str!("../example.fae");
let source = include_str!("test/example.fae");
let theme = ColorTheme::new();
let mut lex = Token::lexer(source);
@ -65,25 +65,4 @@ mod tests {
print!("{}", style.apply_to(lex.slice()));
}
}*/
fn print_ast(source: &str) {
let mut lex = lexer::Lexer::new(source);
let ast = ast::Ast::build(&mut lex);
println!("{:#?}", ast);
}
#[test]
fn ast_fn() {
print_ast(include_str!("ast_fn.fae"));
}
#[test]
fn ast_struct() {
print_ast(include_str!("ast_struct.fae"));
}
#[test]
fn ast_example() {
print_ast(include_str!("../example.fae"));
}
}

View File

@ -1,17 +1,20 @@
use crate::lexer::{Lexer, Token};
#[derive(Debug)]
pub struct Ast<'a> {
pub struct ParseTree<'a> {
pub declarations: Vec<Definition<'a>>,
}
impl<'a> Ast<'a> {
impl<'a> ParseTree<'a> {
pub fn build(lexer: &mut Lexer<'a>) -> Self {
let mut declarations = Vec::new();
let mut associated_struct = None;
while let Some(tok) = lexer.next() {
if let Some(_) = associated_struct {
assert_eq!(Token::Function, tok);
if tok != Token::Function {
lexer.panic_message("Expected fn");
}
declarations.push(Definition::build_function(associated_struct, lexer));
associated_struct = None;
} else {
@ -19,7 +22,7 @@ impl<'a> Ast<'a> {
Token::Struct => declarations.push(Definition::build_structure(lexer)),
Token::Function => declarations.push(Definition::build_function(None, lexer)),
Token::Identifier => associated_struct = Some(lexer.slice()),
_ => panic!("Expected associated struct identifier, fn, or struct"),
_ => lexer.panic_message("Expected associated struct identifier, fn or struct"),
}
}
}
@ -44,7 +47,7 @@ pub enum Definition<'a> {
impl<'a> Definition<'a> {
pub fn build_structure(lexer: &mut Lexer<'a>) -> Self {
let name = lexer.eat_id().unwrap();
assert_eq!(lexer.next(), Some(Token::BraceOpen));
lexer.eat_expect(Token::BraceOpen);
let mut members = Vec::new();
@ -59,7 +62,7 @@ impl<'a> Definition<'a> {
}
Err(Some(Token::Comma)) => {}
Err(Some(Token::BraceClose)) => break,
_ => panic!("Expected comma or closing brace"),
_ => lexer.panic_message("Expected comma or closing brace"),
}
}
@ -67,9 +70,9 @@ impl<'a> Definition<'a> {
}
pub fn build_function(associated_struct: Option<&'a str>, lexer: &mut Lexer<'a>) -> Self {
let name = lexer.eat_id().unwrap();
let name = lexer.eat_expect_id();
lexer.eat_expect(Token::ParanOpen);
assert_eq!(lexer.next(), Some(Token::ParanOpen));
let mut args = Vec::new();
loop {
@ -83,7 +86,7 @@ impl<'a> Definition<'a> {
}
Err(Some(Token::Comma)) => {}
Err(Some(Token::ParanClose)) => break,
_ => panic!("Expected comma, type, or closing paranthases"),
_ => lexer.panic_message("Expected comma, type, or closing parantheses"),
}
}
@ -91,9 +94,10 @@ impl<'a> Definition<'a> {
let mut return_type = None;
if let Some(Token::Identifier) = tok {
return_type = Some(lexer.slice());
assert_eq!(Some(Token::BraceOpen), lexer.next());
} else {
assert_eq!(Some(Token::BraceOpen), tok);
lexer.eat_expect(Token::BraceOpen);
} else if tok != Some(Token::BraceOpen) {
let info = lexer.info();
panic!("Expected open bracket:\n{}", info);
}
let signature = FunctionSignature {
@ -127,7 +131,7 @@ pub struct FunctionSignature<'a> {
impl<'a> FunctionSignature<'a> {
pub fn build(associated_struct: Option<&'a str>, lexer: &mut Lexer<'a>) -> Self {
assert_eq!(lexer.next(), Some(Token::ParanOpen));
lexer.eat_expect(Token::ParanOpen);
let mut args = Vec::new();
loop {
@ -141,7 +145,7 @@ impl<'a> FunctionSignature<'a> {
}
Err(Some(Token::Comma)) => {}
Err(Some(Token::ParanClose)) => break,
_ => panic!("Expected comma, type, or closing paranthases"),
_ => lexer.panic_message("Expected comma, type, or closing parantheses"),
}
}
@ -221,3 +225,29 @@ pub struct BranchBody<'a> {
pub statements: Vec<Statement<'a>>,
pub tail_expression: Box<Expression<'a>>,
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(source: &str) {
let mut lex = Lexer::new(source);
let parse_tree = ParseTree::build(&mut lex);
println!("{:#?}", parse_tree);
}
#[test]
fn function() {
parse(include_str!("test/function.fae"));
}
#[test]
fn structure() {
parse(include_str!("test/structure.fae"));
}
#[test]
fn example() {
parse(include_str!("test/example.fae"));
}
}