single craqte

This commit is contained in:
nora 2023-03-04 11:30:53 +01:00
parent ff78ae710b
commit 2fd78566a3
25 changed files with 36 additions and 64 deletions

197
src/ast.rs Normal file
View file

@ -0,0 +1,197 @@
use std::{ops::Range, path::PathBuf};
#[derive(Debug, Clone, PartialEq)]
pub struct NodeId(u32);
type Span = Range<usize>;
#[derive(Debug, Clone, PartialEq)]
pub struct File {
pub name: PathBuf,
pub items: Vec<Item>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Ty {
pub span: Span,
pub kind: TyKind,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TyKind {
U64,
Ptr(Box<Ty>),
Name(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Item {
FnDecl(FnDecl),
StructDecl(StructDecl),
}
#[derive(Debug, Clone, PartialEq)]
pub struct FnDecl {
pub name: String,
pub params: Vec<NameTyPair>,
pub ret_ty: Option<Ty>,
pub id: NodeId,
pub span: Span,
pub body: Vec<Stmt>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct NameTyPair {
pub name: String,
pub ty: Ty,
pub id: NodeId,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct StructDecl {
pub name: String,
pub fields: Vec<NameTyPair>,
pub id: NodeId,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Stmt {
VarDecl(VarDecl),
Assignment(Assignment),
IfStmt(IfStmt),
WhileStmt(WhileStmt),
LoopStmt(LoopStmt),
Item(Item),
Expr(Expr),
}
#[derive(Debug, Clone, PartialEq)]
pub struct VarDecl {
pub name: String,
pub ty: Option<Ty>,
pub rhs: Option<Expr>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Assignment {
pub place: Expr,
pub rhs: Expr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct IfStmt {
pub cond: Expr,
pub body: Vec<Stmt>,
pub else_part: Option<ElsePart>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ElsePart {
Else(Vec<Stmt>, Span),
ElseIf(Box<IfStmt>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct WhileStmt {
pub cond: Expr,
pub body: Vec<Stmt>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LoopStmt {
pub body: Vec<Stmt>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Expr {
pub kind: ExprKind,
pub id: NodeId,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExprKind {
BinOp(BinOp),
UnaryOp(UnaryOp),
FieldAccess(FieldAccess),
Call(Call),
Literal(Literal),
Name(String),
Array(Vec<Expr>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct BinOp {
pub kind: BinOpKind,
pub lhs: Box<Expr>,
pub rhs: Box<Expr>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BinOpKind {
Eq,
Neq,
Gt,
Lt,
GtEq,
LtEq,
Add,
Sub,
Mul,
Div,
Mod,
Shr,
Shl,
And,
Or,
BitAnd,
BitOr,
Xor,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UnaryOp {
pub expr: Box<Expr>,
pub kind: UnaryOpKind,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UnaryOpKind {
Not,
Neg,
Deref,
AddrOf,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldAccess {
pub expr: Box<Expr>,
pub field_name: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Call {
pub callee: Box<Expr>,
pub args: Vec<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Literal {
String(String, Span),
Integer(u64, Span),
}
impl NodeId {
pub(crate) fn new(id: u32) -> Self {
Self(id)
}
}

193
src/lexer.rs Normal file
View file

@ -0,0 +1,193 @@
use std::fmt::{Debug, Display, Formatter};
use logos::Logos;
#[derive(Logos, Debug, Clone, Hash, PartialEq, Eq)]
pub enum Token<'a> {
#[regex("//[^\n]*", logos::skip)]
Comment,
// punctuation
#[token("{")]
BraceO,
#[token("}")]
BraceC,
#[token("[")]
BracketO,
#[token("]")]
BracketC,
#[token("(")]
ParenO,
#[token(")")]
ParenC,
#[token(".")]
Dot,
#[token(",")]
Comma,
#[token(";")]
Semi,
#[token("=")]
Eq,
#[token("==")]
EqEq,
#[token("!")]
Bang,
#[token("!=")]
BangEq,
#[token(">")]
Greater,
#[token("<")]
Less,
#[token(">=")]
GreaterEq,
#[token("<=")]
LessEq,
#[token("*")]
Asterisk,
#[token("/")]
Slash,
#[token("+")]
Plus,
#[token("-")]
Minus,
#[token("|")]
Or,
#[token("&")]
Ampersand,
#[token("||")]
OrOr,
#[token("&&")]
AndAnd,
#[token("^")]
Caret,
#[token("->")]
Arrow,
#[token(":")]
Colon,
// keywords
#[token("struct")]
Struct,
#[token("fn")]
Fn,
#[token("if")]
If,
#[token("else")]
Else,
#[token("while")]
While,
#[token("loop")]
Loop,
#[token("ptr")]
Ptr,
#[token("let")]
Let,
#[regex(r"[a-zA-Z_]\w*")]
Ident(&'a str),
#[regex(r##""[^"]*""##)]
String(&'a str),
#[regex(r"\d+")]
Integer(&'a str),
#[error]
#[regex(r"[ \t\r\n]+", logos::skip)]
Error,
}
impl<'a> Display for Token<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Token::Comment => f.write_str("comment"),
Token::BraceO => f.write_str("{"),
Token::BraceC => f.write_str("}"),
Token::BracketO => f.write_str("["),
Token::BracketC => f.write_str("]"),
Token::ParenO => f.write_str("("),
Token::ParenC => f.write_str(")"),
Token::Dot => f.write_str("."),
Token::Comma => f.write_str(","),
Token::Semi => f.write_str(";"),
Token::Eq => f.write_str("="),
Token::EqEq => f.write_str("=="),
Token::Bang => f.write_str("!"),
Token::BangEq => f.write_str("!="),
Token::Greater => f.write_str(">"),
Token::Less => f.write_str("<"),
Token::GreaterEq => f.write_str(">="),
Token::LessEq => f.write_str("<="),
Token::Asterisk => f.write_str("*"),
Token::Slash => f.write_str("/"),
Token::Plus => f.write_str("+"),
Token::Minus => f.write_str("-"),
Token::Or => f.write_str("|"),
Token::Ampersand => f.write_str("&"),
Token::OrOr => f.write_str("||"),
Token::AndAnd => f.write_str("&&"),
Token::Caret => f.write_str("^"),
Token::Arrow => f.write_str("->"),
Token::Colon => f.write_str(":"),
Token::Struct => f.write_str("struct"),
Token::Fn => f.write_str("fn"),
Token::If => f.write_str("if"),
Token::Else => f.write_str("else"),
Token::While => f.write_str("while"),
Token::Loop => f.write_str("loop"),
Token::Ptr => f.write_str("ptr"),
Token::Let => f.write_str("let"),
Token::Ident(ident) => write!(f, "identifier `{ident}`"),
Token::String(str) => write!(f, "\"{str}\""),
Token::Integer(int) => write!(f, "{int}"),
Token::Error => f.write_str("error"),
}
}
}
pub fn lex<'src>(code: &'src str) -> logos::Lexer<'_, Token<'src>> {
Token::lexer(code)
}
#[cfg(test)]
mod tests {
use crate::lexer::Token;
fn lex_test(str: &str) -> Vec<Token<'_>> {
let lexer = super::lex(str);
lexer.collect()
}
#[test]
fn punctuation() {
let tokens = lex_test("{} [] () .,; = == != >= <= < > + - * / | || & && ^ -> :");
insta::assert_debug_snapshot!(tokens);
}
#[test]
fn whitespace() {
let tokens = lex_test(
".
\r\n \t .",
);
insta::assert_debug_snapshot!(tokens);
}
#[test]
fn idents() {
let tokens = lex_test("hello w_world b235_");
insta::assert_debug_snapshot!(tokens);
}
#[test]
fn literals() {
let tokens = lex_test(r##""hello friend" 5 "morning" 3263475"##);
insta::assert_debug_snapshot!(tokens);
}
#[test]
fn keywords() {
let tokens = lex_test("struct fn . if else while loop;");
insta::assert_debug_snapshot!(tokens);
}
}

120
src/lib.rs Normal file
View file

@ -0,0 +1,120 @@
#![warn(rust_2018_idioms)]
#![allow(dead_code)]
use std::path::PathBuf;
use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
use chumsky::prelude::Simple;
use logos::Logos;
use crate::lexer::Token;
mod ast;
mod lexer;
mod parser;
mod pretty;
pub fn parse(_str: &str, _file_name: PathBuf) -> Result<ast::File, ()> {
todo!()
}
pub fn test() {
let src = "
fn main(uwu: u64, owo: ptr WOW) -> ptr u64 {
let uwu = &1;
let owo = yeet(1+2*3, AA);
if 1 {
10;
} else {}
if 1 { if 1 { if 1 {} } }
}
fn aa() {}
";
let lexer = Token::lexer(src);
let len = lexer.source().len();
let state = parser::ParserState::default();
let (file, errors) = parser::parse(lexer.spanned(), &state, len, "test_file".into());
if let Some(file) = file {
println!("{}", pretty::pretty_print_ast(&file));
}
report_errors(src, errors);
}
fn report_errors(src: &str, errors: Vec<Simple<Token<'_>>>) {
errors
.into_iter()
.map(|e| e.map(|c| c.to_string()))
.for_each(|e| {
let report = Report::build(ReportKind::Error, (), e.span().start);
let report = match e.reason() {
chumsky::error::SimpleReason::Unclosed { span, delimiter } => report
.with_message(format!(
"Unclosed delimiter {}",
delimiter.fg(Color::Yellow)
))
.with_label(
Label::new(span.clone())
.with_message(format!(
"Unclosed delimiter {}",
delimiter.fg(Color::Yellow)
))
.with_color(Color::Yellow),
)
.with_label(
Label::new(e.span())
.with_message(format!(
"Must be closed before this {}",
e.found()
.unwrap_or(&"end of file".to_string())
.fg(Color::Red)
))
.with_color(Color::Red),
),
chumsky::error::SimpleReason::Unexpected => report
.with_message(format!(
"{}, expected {}",
if e.found().is_some() {
"Unexpected token in input"
} else {
"Unexpected end of input"
},
if e.expected().len() == 0 {
"something else".to_string()
} else {
e.expected()
.map(|expected| match expected {
Some(expected) => expected.to_string(),
None => "end of input".to_string(),
})
.collect::<Vec<_>>()
.join(", ")
}
))
.with_label(
Label::new(e.span())
.with_message(format!(
"Unexpected token {}",
e.found()
.unwrap_or(&"end of file".to_string())
.fg(Color::Red)
))
.with_color(Color::Red),
),
chumsky::error::SimpleReason::Custom(msg) => report.with_message(msg).with_label(
Label::new(e.span())
.with_message(format!("{}", msg.fg(Color::Red)))
.with_color(Color::Red),
),
};
report.finish().print(Source::from(&src)).unwrap();
});
}

View file

@ -1,4 +1,3 @@
fn main() {
parser::test();
ub::test();
}

517
src/parser.rs Normal file
View file

@ -0,0 +1,517 @@
use std::{cell::Cell, ops::Range, path::PathBuf};
use chumsky::{prelude::*, Stream};
use crate::{
ast::{
Assignment, BinOp, BinOpKind, Call, ElsePart, Expr, ExprKind, File, FnDecl, IfStmt, Item,
Literal, NameTyPair, NodeId, Stmt, StructDecl, Ty, TyKind, UnaryOp, UnaryOpKind, VarDecl,
WhileStmt,
},
lexer::Token,
};
type Error<'src> = Simple<Token<'src>>;
type Span = Range<usize>;
#[derive(Default)]
pub struct ParserState {
next_id: Cell<u32>,
}
impl ParserState {
pub fn next_id(&self) -> NodeId {
let next = self.next_id.get();
self.next_id.set(next + 1);
NodeId::new(next)
}
}
fn ident_parser<'src>() -> impl Parser<Token<'src>, String, Error = Error<'src>> + Clone {
let ident = select! {
Token::Ident(ident) => ident.to_owned(),
};
ident.labelled("identifier").boxed()
}
fn ty_parser<'src>() -> impl Parser<Token<'src>, Ty, Error = Error<'src>> + Clone {
recursive(|ty_parser| {
let primitive = filter_map(|span, token| {
let kind = match token {
Token::Ident("u64") => TyKind::U64,
_ => return Err(Simple::expected_input_found(span, Vec::new(), Some(token))),
};
Ok(Ty { span, kind })
})
.labelled("primitive type");
let ptr = just(Token::Ptr)
.ignore_then(ty_parser.clone())
.map_with_span(|ty: Ty, span| Ty {
kind: TyKind::Ptr(Box::new(ty)),
span,
})
.labelled("pointer type");
let name = ident_parser()
.map_with_span(|name: String, span| Ty {
kind: TyKind::Name(name),
span,
})
.labelled("name type");
primitive.or(ptr).or(name).labelled("type").boxed()
})
}
fn expr_parser<'src>(
state: &'src ParserState,
) -> impl Parser<Token<'src>, Expr, Error = Error<'src>> + Clone + 'src {
recursive(|expr| {
let literal = filter_map(|span: Span, token| match token {
Token::String(str) => Ok(Expr {
kind: ExprKind::Literal(Literal::String(
str[1..str.len() - 2].to_owned(),
span.clone(),
)),
id: state.next_id(),
span,
}),
// todo lol unwrap
Token::Integer(int) => Ok(Expr {
kind: ExprKind::Literal(Literal::Integer(int.parse().unwrap(), span.clone())),
id: state.next_id(),
span,
}),
_ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))),
})
.labelled("literal");
let expr_list = expr
.clone()
.separated_by(just(Token::Comma))
.allow_trailing()
.or_not()
.map(|item| item.unwrap_or_default())
.boxed();
let array = expr_list
.clone()
.delimited_by(just(Token::BracketO), just(Token::BracketC))
.map_with_span(|exprs: Vec<Expr>, span| Expr {
kind: ExprKind::Array(exprs),
id: state.next_id(),
span,
});
let atom = literal
.or(ident_parser().map_with_span(|name, span| Expr {
kind: ExprKind::Name(name),
id: state.next_id(),
span,
}))
.or(array)
.or(expr
.clone()
.delimited_by(just(Token::ParenO), just(Token::ParenC)))
.boxed();
let call = atom
.clone()
.then(
expr_list
.delimited_by(just(Token::ParenO), just(Token::ParenC))
.repeated(),
)
.foldl(|callee: Expr, args: Vec<Expr>| {
let span =
callee.span.start..args.last().map(|e| e.span.end).unwrap_or(callee.span.end);
Expr {
kind: ExprKind::Call(Call {
callee: Box::new(callee),
args,
}),
id: state.next_id(),
span,
}
})
.labelled("call")
.boxed();
let unary_op = choice((
just(Token::Minus).to(UnaryOpKind::Neg),
just(Token::Bang).to(UnaryOpKind::Not),
just(Token::Ampersand).to(UnaryOpKind::AddrOf),
just(Token::Asterisk).to(UnaryOpKind::Deref),
))
.repeated()
.then(call)
.foldr(|kind, rhs| {
let span = rhs.span.clone();
Expr {
kind: ExprKind::UnaryOp(UnaryOp {
expr: Box::new(rhs),
kind,
span: span.clone(),
}),
id: state.next_id(),
span,
}
})
.labelled("unary")
.boxed();
let op = just(Token::Asterisk)
.to(BinOpKind::Mul)
.or(just(Token::Slash).to(BinOpKind::Div));
let product = unary_op
.clone()
.then(op.then(unary_op).repeated())
.foldl(|a, (kind, b)| {
let span = a.span.start..b.span.end;
Expr {
kind: ExprKind::BinOp(BinOp {
kind,
lhs: Box::new(a),
rhs: Box::new(b),
span: span.clone(),
}),
id: state.next_id(),
span,
}
});
// Sum ops (add and subtract) have equal precedence
let op = just(Token::Plus)
.to(BinOpKind::Add)
.or(just(Token::Minus).to(BinOpKind::Sub));
let sum = product
.clone()
.then(op.then(product).repeated())
.foldl(|a, (kind, b)| {
let span = a.span.start..b.span.end;
Expr {
kind: ExprKind::BinOp(BinOp {
kind,
lhs: Box::new(a),
rhs: Box::new(b),
span: span.clone(),
}),
id: state.next_id(),
span,
}
})
.labelled("product")
.boxed();
// Comparison ops (equal, not-equal) have equal precedence
let op = just(Token::EqEq)
.to(BinOpKind::Eq)
.or(just(Token::BangEq).to(BinOpKind::Neq));
let compare = sum
.clone()
.then(op.then(sum).repeated())
.foldl(|a, (kind, b)| {
let span = a.span.start..b.span.end;
Expr {
kind: ExprKind::BinOp(BinOp {
kind,
lhs: Box::new(a),
rhs: Box::new(b),
span: span.clone(),
}),
id: state.next_id(),
span,
}
});
compare.labelled("comparison").boxed()
})
}
fn statement_parser<'src>(
state: &'src ParserState,
) -> impl Parser<Token<'src>, Stmt, Error = Error<'src>> + Clone {
recursive(|stmt| {
let var_decl = just(Token::Let)
.ignore_then(ident_parser())
.then(just(Token::Colon).ignore_then(ty_parser()).or_not())
.then(just(Token::Eq).ignore_then(expr_parser(state)).or_not())
.then_ignore(just(Token::Semi))
.map(|((name, ty), rhs)| {
Stmt::VarDecl(VarDecl {
name,
ty,
rhs,
span: Default::default(),
})
})
.boxed();
let assignment = expr_parser(state)
.then_ignore(just(Token::Eq))
.then(expr_parser(state))
.then_ignore(just(Token::Semi))
.map(|(place, rhs)| {
Stmt::Assignment(Assignment {
place,
rhs,
span: Default::default(),
})
});
let block = stmt
.clone()
.repeated()
.delimited_by(just(Token::BraceO), just(Token::BraceC));
let while_loop = just(Token::While)
.ignore_then(expr_parser(state))
.then(block.clone())
.map_with_span(|(cond, body), span| Stmt::WhileStmt(WhileStmt { cond, body, span }))
.labelled("while loop");
let if_stmt = recursive(|if_stmt| {
just(Token::If)
.ignore_then(expr_parser(state))
.then(block.clone())
.then(
just(Token::Else)
.ignore_then(
if_stmt
.map(|if_stmt| ElsePart::ElseIf(Box::new(if_stmt)))
.or(block.clone().map_with_span(ElsePart::Else)),
)
.or_not(),
)
.map_with_span(|((cond, body), else_part), span| IfStmt {
cond,
body,
else_part,
span,
})
})
.map(Stmt::IfStmt)
.boxed();
var_decl
.or(assignment)
.or(expr_parser(state)
.then_ignore(just(Token::Semi))
.map(Stmt::Expr))
.or(if_stmt)
.or(while_loop)
})
.labelled("statement")
.boxed()
}
fn name_ty_pair_parser<'src>(
state: &'src ParserState,
) -> impl Parser<Token<'src>, NameTyPair, Error = Error<'src>> + Clone {
ident_parser()
.then_ignore(just(Token::Colon))
.then(ty_parser())
.map_with_span(|(name, ty), span| NameTyPair {
name,
ty,
id: state.next_id(),
span,
})
}
fn struct_parser<'src>(
state: &'src ParserState,
) -> impl Parser<Token<'src>, StructDecl, Error = Error<'src>> + Clone {
let name = just(Token::Struct).ignore_then(ident_parser());
let fields = name_ty_pair_parser(state)
.separated_by(just(Token::Comma))
.delimited_by(just(Token::BraceO), just(Token::BraceC));
name.then(fields)
.map(|(name, fields)| StructDecl {
name,
fields,
id: state.next_id(),
span: Default::default(),
})
.labelled("struct")
}
fn item_parser<'src>(
state: &'src ParserState,
) -> impl Parser<Token<'src>, Item, Error = Error<'src>> + Clone {
// ---- function
let name = ident_parser();
let params = name_ty_pair_parser(state)
.separated_by(just(Token::Comma))
.allow_trailing()
.delimited_by(just(Token::ParenO), just(Token::ParenC))
.labelled("function arguments");
let ret_ty = just(Token::Arrow).ignore_then(ty_parser()).or_not();
let function = just(Token::Fn)
.ignore_then(name)
.then(params)
.then(ret_ty)
.then(
statement_parser(state)
.repeated()
.delimited_by(just(Token::BraceO), just(Token::BraceC)),
)
.map_with_span(|(((name, params), ret_ty), body), span| FnDecl {
name,
params,
ret_ty,
id: state.next_id(),
span,
body,
})
.labelled("function");
// ---- item
function
.map(Item::FnDecl)
.or(struct_parser(state).map(Item::StructDecl))
.labelled("item")
}
fn file_parser<'src>(
file_name: PathBuf,
state: &'src ParserState,
) -> impl Parser<Token<'src>, File, Error = Error<'src>> + Clone {
item_parser(state)
.repeated()
.then_ignore(end())
.map(move |items| File {
name: file_name.clone(),
items,
})
.labelled("file")
}
pub fn parse<'src, I>(
lexer: I,
state: &'src ParserState,
len: usize,
file_name: PathBuf,
) -> (Option<File>, Vec<Error<'src>>)
where
I: 'src,
I: Iterator<Item = (Token<'src>, Span)>,
{
file_parser(file_name, state).parse_recovery_verbose(Stream::from_iter(len..len + 1, lexer))
}
#[cfg(test)]
mod tests {
use std::{fmt::Debug, path::PathBuf};
use logos::Logos;
use super::ParserState;
use crate::lexer::Token;
fn parse<'src>(src: &'src str, state: &'src ParserState) -> impl Debug + 'src {
let lexer = Token::lexer(src);
let len = lexer.source().len();
super::parse(
lexer.spanned(),
state,
len,
PathBuf::from(module_path!().replace("::", "__")),
)
}
#[test]
fn addition() {
let state = ParserState::default();
let r = parse("fn main() { 1 + 4; }", &state);
insta::assert_debug_snapshot!(r);
}
#[test]
fn expression() {
let state = ParserState::default();
let r = parse("fn main() { (4 / hallo()) + 5; }", &state);
insta::assert_debug_snapshot!(r);
}
#[test]
fn unary() {
let state = ParserState::default();
let r = parse(
"fn main() {
-(*5);
&5;
2 + &8;
*6 * *8; // :)
}",
&state,
);
insta::assert_debug_snapshot!(r);
}
#[test]
fn function() {
let state = ParserState::default();
let r = parse("fn foo() -> u64 { 1 + 5; }", &state);
insta::assert_debug_snapshot!(r);
}
#[test]
fn if_no_else() {
let state = ParserState::default();
let r = parse("fn foo() -> u64 { if false {} }", &state);
insta::assert_debug_snapshot!(r);
}
#[test]
fn if_else() {
let state = ParserState::default();
let r = parse("fn foo() -> u64 { if false {} else {} }", &state);
insta::assert_debug_snapshot!(r);
}
#[test]
fn while_loop() {
let state = ParserState::default();
let r = parse("fn foo() -> u64 { while false {} }", &state);
insta::assert_debug_snapshot!(r);
}
#[test]
fn var_decl() {
let state = ParserState::default();
let r = parse("fn foo() -> u64 { let hello: u64 = 5; let owo = 0; let nice: u64; let nothing; }", &state);
insta::assert_debug_snapshot!(r);
}
#[test]
fn struct_() {
let state = ParserState::default();
let r = parse("struct X { y: u64, x: u64 }", &state);
insta::assert_debug_snapshot!(r);
}
#[test]
fn types() {
let state = ParserState::default();
let r = parse(
"fn types() -> ptr u64 { let test: Test = 2; let int: ptr u64 = 25; }",
&state,
);
insta::assert_debug_snapshot!(r);
}
}

277
src/pretty.rs Normal file
View file

@ -0,0 +1,277 @@
use std::fmt::Write;
use crate::ast::{
BinOpKind, ElsePart, Expr, ExprKind, File, IfStmt, Item, Literal, NameTyPair, Stmt, Ty, TyKind,
UnaryOpKind,
};
pub fn pretty_print_ast(ast: &File) -> String {
let mut printer = Printer {
out: String::new(),
indent: 0,
};
printer.print_items(&ast.items);
printer.out
}
struct Printer {
out: String,
indent: usize,
}
impl Printer {
fn print_items(&mut self, items: &[Item]) {
for item in items {
self.print_item(item);
}
}
fn print_item(&mut self, item: &Item) {
match item {
Item::FnDecl(fn_decl) => {
self.word("fn ");
self.word(&fn_decl.name);
self.word("(");
let params = &fn_decl.params;
if params.len() > 0 {
self.linebreak_indent();
for i in 0..params.len().saturating_sub(1) {
let param = &fn_decl.params[i];
self.print_name_ty(param);
self.word(",");
self.linebreak();
}
if params.len() > 0 {
self.print_name_ty(params.last().unwrap());
}
self.linebreak_unindent();
}
self.word(") ");
if let Some(ret_ty) = &fn_decl.ret_ty {
self.word("-> ");
self.print_ty(ret_ty);
self.word(" ");
}
self.print_block(&fn_decl.body);
self.linebreak();
}
Item::StructDecl(_) => {
todo!()
}
}
}
fn print_name_ty(&mut self, name_ty: &NameTyPair) {
self.word(&name_ty.name);
self.word(": ");
self.print_ty(&name_ty.ty);
}
fn print_ty(&mut self, ty: &Ty) {
match &ty.kind {
TyKind::U64 => self.word("u64"),
TyKind::Name(name) => self.word(name),
TyKind::Ptr(ty) => {
self.word("ptr ");
self.print_ty(ty);
}
}
}
/// Starts off after the `{`. Indents itself first if necessary. Stops at the place where `}` should be inserted.
fn print_block(&mut self, stmts: &[Stmt]) {
self.word("{");
if let [first, rest @ ..] = &stmts {
self.linebreak_indent();
self.print_stmt(first);
for stmt in rest {
self.linebreak();
self.print_stmt(stmt);
}
self.linebreak_unindent();
}
self.word("}");
}
/// Leaves the cursor after the semicolon
fn print_stmt(&mut self, stmt: &Stmt) {
match stmt {
Stmt::VarDecl(decl) => {
self.word("let ");
self.word(&decl.name);
if let Some(ty) = &decl.ty {
self.word(": ");
self.print_ty(ty);
}
if let Some(rhs) = &decl.rhs {
self.word(" = ");
self.print_expr(rhs);
}
self.word(";");
}
Stmt::Assignment(assign) => {
self.print_expr(&assign.place);
self.word(" = ");
self.print_expr(&assign.rhs);
self.word(";");
}
Stmt::IfStmt(if_stmt) => {
self.print_if(if_stmt);
}
Stmt::WhileStmt(while_stmt) => {
self.word("while ");
self.print_expr(&while_stmt.cond);
self.print_block(&while_stmt.body);
}
Stmt::LoopStmt(loop_stmt) => {
self.word("loop ");
self.print_block(&loop_stmt.body);
}
Stmt::Item(item) => {
self.print_item(item);
}
Stmt::Expr(expr) => {
self.print_expr(expr);
self.word(";");
}
}
}
fn print_if(&mut self, if_stmt: &IfStmt) {
self.word("if ");
self.print_expr(&if_stmt.cond);
self.word(" ");
self.print_block(&if_stmt.body);
if let Some(else_part) = &if_stmt.else_part {
self.word(" else ");
match else_part {
ElsePart::Else(stmts, _) => {
self.print_block(stmts);
}
ElsePart::ElseIf(if_stmt) => {
self.print_if(if_stmt);
}
}
}
}
fn print_expr(&mut self, expr: &Expr) {
match &expr.kind {
ExprKind::BinOp(bin_op) => {
self.print_expr_wrapped(&bin_op.lhs);
self.word(match bin_op.kind {
BinOpKind::Eq => " == ",
BinOpKind::Neq => " != ",
BinOpKind::Gt => " > ",
BinOpKind::Lt => " < ",
BinOpKind::GtEq => " >= ",
BinOpKind::LtEq => " <= ",
BinOpKind::Add => " + ",
BinOpKind::Sub => " - ",
BinOpKind::Mul => " * ",
BinOpKind::Div => " / ",
BinOpKind::Mod => " % ",
BinOpKind::Shr => " >> ",
BinOpKind::Shl => " << ",
BinOpKind::And => " && ",
BinOpKind::Or => " || ",
BinOpKind::BitAnd => " & ",
BinOpKind::BitOr => " | ",
BinOpKind::Xor => " ^ ",
});
self.print_expr_wrapped(&bin_op.rhs);
}
ExprKind::UnaryOp(unary_op) => {
self.word(match unary_op.kind {
UnaryOpKind::Not => "!",
UnaryOpKind::Neg => "-",
UnaryOpKind::Deref => "*",
UnaryOpKind::AddrOf => "&",
});
self.print_expr_wrapped(&unary_op.expr);
}
ExprKind::FieldAccess(field_access) => {
self.print_expr(&field_access.expr);
self.word(".");
self.word(&field_access.field_name);
}
ExprKind::Call(call) => {
self.print_expr(&call.callee);
self.word("(");
if let [first, rest @ ..] = &*call.args {
self.print_expr(first);
for expr in rest {
self.word(", ");
self.print_expr(expr);
}
}
self.word(")");
}
ExprKind::Literal(literal) => match literal {
Literal::Integer(int, _) => write!(self.out, "{int}").unwrap(),
Literal::String(string, _) => {
self.word("\"");
// FIXME: Handle escapes.
self.word(string);
self.word("\"");
}
},
ExprKind::Name(name) => {
self.word(name);
}
ExprKind::Array(exprs) => {
self.word("[");
if let [first, rest @ ..] = exprs.as_slice() {
self.print_expr(first);
for expr in rest {
self.word(", ");
self.print_expr(expr);
}
}
self.word("]");
}
}
}
fn print_expr_wrapped(&mut self, expr: &Expr) {
match expr.kind {
ExprKind::Literal(_)
| ExprKind::Array(_)
| ExprKind::Call(_)
| ExprKind::Name(_)
| ExprKind::FieldAccess(_) => {
self.print_expr(expr);
}
ExprKind::BinOp(_) | ExprKind::UnaryOp(_) => {
self.word("(");
self.print_expr(expr);
self.word(")");
}
}
}
// utility functions
fn word(&mut self, word: &str) {
self.out.push_str(word);
}
fn linebreak_indent(&mut self) {
self.indent += 1;
self.linebreak();
}
fn linebreak_unindent(&mut self) {
self.indent -= 1;
self.linebreak();
}
fn linebreak(&mut self) {
self.word("\n");
self.word(&" ".repeat(self.indent))
}
}

View file

@ -0,0 +1,15 @@
---
source: src/lexer.rs
expression: tokens
---
[
Ident(
"hello",
),
Ident(
"w_world",
),
Ident(
"b235_",
),
]

View file

@ -0,0 +1,14 @@
---
source: src/lexer.rs
expression: tokens
---
[
Struct,
Fn,
Dot,
If,
Else,
While,
Loop,
Semi,
]

View file

@ -0,0 +1,18 @@
---
source: src/lexer.rs
expression: tokens
---
[
String(
"\"hello friend\"",
),
Integer(
"5",
),
String(
"\"morning\"",
),
Integer(
"3263475",
),
]

View file

@ -0,0 +1,33 @@
---
source: src/lexer.rs
expression: tokens
---
[
BraceO,
BraceC,
BracketO,
BracketC,
ParenO,
ParenC,
Dot,
Comma,
Semi,
Eq,
EqEq,
BangEq,
GreaterEq,
LessEq,
Less,
Greater,
Plus,
Minus,
Asterisk,
Slash,
Or,
OrOr,
Ampersand,
AndAnd,
Caret,
Arrow,
Colon,
]

View file

@ -0,0 +1,8 @@
---
source: src/lexer.rs
expression: tokens
---
[
Dot,
Dot,
]

View file

@ -0,0 +1,65 @@
---
source: src/parser.rs
expression: r
---
(
Some(
File {
name: "ub__parser__tests",
items: [
FnDecl(
FnDecl {
name: "main",
params: [],
ret_ty: None,
id: NodeId(
6,
),
span: 0..20,
body: [
Expr(
Expr {
kind: BinOp(
BinOp {
kind: Add,
lhs: Expr {
kind: Literal(
Integer(
1,
12..13,
),
),
id: NodeId(
3,
),
span: 12..13,
},
rhs: Expr {
kind: Literal(
Integer(
4,
16..17,
),
),
id: NodeId(
4,
),
span: 16..17,
},
span: 12..17,
},
),
id: NodeId(
5,
),
span: 12..17,
},
),
],
},
),
],
},
),
[],
)

View file

@ -0,0 +1,97 @@
---
source: src/parser.rs
expression: r
---
(
Some(
File {
name: "ub__parser__tests",
items: [
FnDecl(
FnDecl {
name: "main",
params: [],
ret_ty: None,
id: NodeId(
12,
),
span: 0..32,
body: [
Expr(
Expr {
kind: BinOp(
BinOp {
kind: Add,
lhs: Expr {
kind: BinOp(
BinOp {
kind: Div,
lhs: Expr {
kind: Literal(
Integer(
4,
13..14,
),
),
id: NodeId(
6,
),
span: 13..14,
},
rhs: Expr {
kind: Call(
Call {
callee: Expr {
kind: Name(
"hallo",
),
id: NodeId(
7,
),
span: 17..22,
},
args: [],
},
),
id: NodeId(
8,
),
span: 17..22,
},
span: 13..22,
},
),
id: NodeId(
9,
),
span: 13..22,
},
rhs: Expr {
kind: Literal(
Integer(
5,
28..29,
),
),
id: NodeId(
10,
),
span: 28..29,
},
span: 13..29,
},
),
id: NodeId(
11,
),
span: 13..29,
},
),
],
},
),
],
},
),
[],
)

View file

@ -0,0 +1,70 @@
---
source: src/parser.rs
expression: r
---
(
Some(
File {
name: "ub__parser__tests",
items: [
FnDecl(
FnDecl {
name: "foo",
params: [],
ret_ty: Some(
Ty {
span: 12..15,
kind: U64,
},
),
id: NodeId(
6,
),
span: 0..26,
body: [
Expr(
Expr {
kind: BinOp(
BinOp {
kind: Add,
lhs: Expr {
kind: Literal(
Integer(
1,
18..19,
),
),
id: NodeId(
3,
),
span: 18..19,
},
rhs: Expr {
kind: Literal(
Integer(
5,
22..23,
),
),
id: NodeId(
4,
),
span: 22..23,
},
span: 18..23,
},
),
id: NodeId(
5,
),
span: 18..23,
},
),
],
},
),
],
},
),
[],
)

View file

@ -0,0 +1,53 @@
---
source: src/parser.rs
expression: r
---
(
Some(
File {
name: "ub__parser__tests",
items: [
FnDecl(
FnDecl {
name: "foo",
params: [],
ret_ty: Some(
Ty {
span: 12..15,
kind: U64,
},
),
id: NodeId(
1,
),
span: 0..39,
body: [
IfStmt(
IfStmt {
cond: Expr {
kind: Name(
"false",
),
id: NodeId(
0,
),
span: 21..26,
},
body: [],
else_part: Some(
Else(
[],
35..37,
),
),
span: 18..37,
},
),
],
},
),
],
},
),
[],
)

View file

@ -0,0 +1,48 @@
---
source: src/parser.rs
expression: r
---
(
Some(
File {
name: "ub__parser__tests",
items: [
FnDecl(
FnDecl {
name: "foo",
params: [],
ret_ty: Some(
Ty {
span: 12..15,
kind: U64,
},
),
id: NodeId(
1,
),
span: 0..31,
body: [
IfStmt(
IfStmt {
cond: Expr {
kind: Name(
"false",
),
id: NodeId(
0,
),
span: 21..26,
},
body: [],
else_part: None,
span: 18..29,
},
),
],
},
),
],
},
),
[],
)

View file

@ -0,0 +1,47 @@
---
source: src/parser.rs
expression: r
---
(
Some(
File {
name: "ub__parser__tests",
items: [
StructDecl(
StructDecl {
name: "X",
fields: [
NameTyPair {
name: "y",
ty: Ty {
span: 14..17,
kind: U64,
},
id: NodeId(
0,
),
span: 11..17,
},
NameTyPair {
name: "x",
ty: Ty {
span: 22..25,
kind: U64,
},
id: NodeId(
1,
),
span: 19..25,
},
],
id: NodeId(
2,
),
span: 0..0,
},
),
],
},
),
[],
)

View file

@ -0,0 +1,96 @@
---
source: src/parser.rs
expression: r
---
(
Some(
File {
name: "ub__parser__tests",
items: [
FnDecl(
FnDecl {
name: "types",
params: [],
ret_ty: Some(
Ty {
span: 14..21,
kind: Ptr(
Ty {
span: 18..21,
kind: U64,
},
),
},
),
id: NodeId(
2,
),
span: 0..68,
body: [
VarDecl(
VarDecl {
name: "test",
ty: Some(
Ty {
span: 34..38,
kind: Name(
"Test",
),
},
),
rhs: Some(
Expr {
kind: Literal(
Integer(
2,
41..42,
),
),
id: NodeId(
0,
),
span: 41..42,
},
),
span: 0..0,
},
),
VarDecl(
VarDecl {
name: "int",
ty: Some(
Ty {
span: 53..60,
kind: Ptr(
Ty {
span: 57..60,
kind: U64,
},
),
},
),
rhs: Some(
Expr {
kind: Literal(
Integer(
25,
63..65,
),
),
id: NodeId(
1,
),
span: 63..65,
},
),
span: 0..0,
},
),
],
},
),
],
},
),
[],
)

View file

@ -0,0 +1,203 @@
---
source: src/parser.rs
expression: r
---
(
Some(
File {
name: "ub__parser__tests",
items: [
FnDecl(
FnDecl {
name: "main",
params: [],
ret_ty: None,
id: NodeId(
28,
),
span: 0..63,
body: [
Expr(
Expr {
kind: UnaryOp(
UnaryOp {
expr: Expr {
kind: UnaryOp(
UnaryOp {
expr: Expr {
kind: Literal(
Integer(
5,
19..20,
),
),
id: NodeId(
3,
),
span: 19..20,
},
kind: Deref,
span: 19..20,
},
),
id: NodeId(
4,
),
span: 19..20,
},
kind: Neg,
span: 19..20,
},
),
id: NodeId(
5,
),
span: 19..20,
},
),
Expr(
Expr {
kind: UnaryOp(
UnaryOp {
expr: Expr {
kind: Literal(
Integer(
5,
28..29,
),
),
id: NodeId(
8,
),
span: 28..29,
},
kind: AddrOf,
span: 28..29,
},
),
id: NodeId(
9,
),
span: 28..29,
},
),
Expr(
Expr {
kind: BinOp(
BinOp {
kind: Add,
lhs: Expr {
kind: Literal(
Integer(
2,
35..36,
),
),
id: NodeId(
14,
),
span: 35..36,
},
rhs: Expr {
kind: UnaryOp(
UnaryOp {
expr: Expr {
kind: Literal(
Integer(
8,
40..41,
),
),
id: NodeId(
15,
),
span: 40..41,
},
kind: AddrOf,
span: 40..41,
},
),
id: NodeId(
16,
),
span: 40..41,
},
span: 35..41,
},
),
id: NodeId(
17,
),
span: 35..41,
},
),
Expr(
Expr {
kind: BinOp(
BinOp {
kind: Mul,
lhs: Expr {
kind: UnaryOp(
UnaryOp {
expr: Expr {
kind: Literal(
Integer(
6,
48..49,
),
),
id: NodeId(
23,
),
span: 48..49,
},
kind: Deref,
span: 48..49,
},
),
id: NodeId(
24,
),
span: 48..49,
},
rhs: Expr {
kind: UnaryOp(
UnaryOp {
expr: Expr {
kind: Literal(
Integer(
8,
53..54,
),
),
id: NodeId(
25,
),
span: 53..54,
},
kind: Deref,
span: 53..54,
},
),
id: NodeId(
26,
),
span: 53..54,
},
span: 48..54,
},
),
id: NodeId(
27,
),
span: 48..54,
},
),
],
},
),
],
},
),
[],
)

View file

@ -0,0 +1,100 @@
---
source: src/parser.rs
expression: r
---
(
Some(
File {
name: "ub__parser__tests",
items: [
FnDecl(
FnDecl {
name: "foo",
params: [],
ret_ty: Some(
Ty {
span: 12..15,
kind: U64,
},
),
id: NodeId(
2,
),
span: 0..80,
body: [
VarDecl(
VarDecl {
name: "hello",
ty: Some(
Ty {
span: 29..32,
kind: U64,
},
),
rhs: Some(
Expr {
kind: Literal(
Integer(
5,
35..36,
),
),
id: NodeId(
0,
),
span: 35..36,
},
),
span: 0..0,
},
),
VarDecl(
VarDecl {
name: "owo",
ty: None,
rhs: Some(
Expr {
kind: Literal(
Integer(
0,
48..49,
),
),
id: NodeId(
1,
),
span: 48..49,
},
),
span: 0..0,
},
),
VarDecl(
VarDecl {
name: "nice",
ty: Some(
Ty {
span: 61..64,
kind: U64,
},
),
rhs: None,
span: 0..0,
},
),
VarDecl(
VarDecl {
name: "nothing",
ty: None,
rhs: None,
span: 0..0,
},
),
],
},
),
],
},
),
[],
)

View file

@ -0,0 +1,47 @@
---
source: src/parser.rs
expression: r
---
(
Some(
File {
name: "ub__parser__tests",
items: [
FnDecl(
FnDecl {
name: "foo",
params: [],
ret_ty: Some(
Ty {
span: 12..15,
kind: U64,
},
),
id: NodeId(
1,
),
span: 0..34,
body: [
WhileStmt(
WhileStmt {
cond: Expr {
kind: Name(
"false",
),
id: NodeId(
0,
),
span: 24..29,
},
body: [],
span: 18..32,
},
),
],
},
),
],
},
),
[],
)