Skip to content

Commit

Permalink
feat: basic chumsky parser
Browse files Browse the repository at this point in the history
  • Loading branch information
kaylendog committed Dec 5, 2022
1 parent 1009c83 commit d89a28e
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 28 deletions.
1 change: 1 addition & 0 deletions compiler/fluxc_ast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ authors = ["SkyezerFox <actuallyori@gmail.com>"]
edition = "2021"

[dependencies]
chumsky = "0.8"
log = "0.4"

fluxc_span = { path = "../fluxc_span" }
Expand Down
22 changes: 22 additions & 0 deletions compiler/fluxc_ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use std::ops::Range;

use chumsky::Span;
use fluxc_types::{Type, Typed};

mod expr;
Expand Down Expand Up @@ -59,6 +60,27 @@ impl Node<()> {
}
}

impl<T: Clone> Span for Node<T> {
type Context = T;
type Offset = usize;

fn new(context: Self::Context, range: Range<Self::Offset>) -> Self {
Node { value: context, span: range }
}

fn context(&self) -> Self::Context {
self.value.clone()
}

fn start(&self) -> Self::Offset {
self.span.start
}

fn end(&self) -> Self::Offset {
self.span.end
}
}

/// Generic implemetation of typed for all nodes
impl<T: Typed> Typed for Node<T> {
fn type_of(&self) -> Type {
Expand Down
19 changes: 16 additions & 3 deletions compiler/fluxc_lexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{fmt::Display, ops::Range};
use logos::Logos;

/// A token lexed by the Flux lexer.
#[derive(Logos, Debug, PartialEq, Clone)]
#[derive(Logos, Debug, PartialEq, Eq, Clone, Hash)]
pub enum Token {
#[regex(r"[ \t\n\f]+", logos::skip)]
#[error]
Expand Down Expand Up @@ -55,6 +55,12 @@ pub enum Token {
#[token("-=")]
TokenMinusEq,

#[token("*=")]
TokenMulEq,

#[token("/=")]
TokenDivEq,

#[token("!=")]
TokenNe,

Expand Down Expand Up @@ -148,8 +154,13 @@ pub enum Token {
#[regex("-?[0-9]+", |lex| lex.slice().parse())]
LiteralInt(i64),

#[regex("[0-9]*\\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+", |lex| lex.slice().parse())]
LiteralFloat(f64),
// use raw float bytes since floats are not hashable
// chumsky needs Hash on its input type
#[regex(
"[0-9]*\\.[0-9]+([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+",
|lex| lex.slice().parse::<f64>().map(f64::to_be_bytes)
)]
LiteralFloat([u8; 8]),

#[regex(r#""([^"\\]|\\t|\\u|\\n|\\")*""#, |lex| lex.slice().to_string())]
LiteralStr(String),
Expand Down Expand Up @@ -217,6 +228,8 @@ impl Display for Token {
Token::TokenNot => "!",
Token::TokenLogicalAnd => "&&",
Token::TokenLogicalOr => "||",
Token::TokenMulEq => "*=",
Token::TokenDivEq => "/=",
}
)
}
Expand Down
10 changes: 5 additions & 5 deletions compiler/fluxc_lexer/tests/fibonacci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ use pretty_assertions::assert_eq;
#[test]
fn test_lex_fibonacci() {
let src = include_str!("./fibonacci.flx");
let tokens = lex(src);
let tokens = lex(src).unwrap();
assert_eq!(
Ok(vec![
vec![
(Token::KeywordLet, 0..3),
(Token::Ident("x".to_string()), 4..5),
(Token::TokenComma, 5..6),
(Token::Ident("y".to_string()), 7..8),
(Token::TokenAssign, 9..10),
(Token::LiteralInt("1".to_string()), 11..12),
(Token::LiteralInt(1), 11..12),
(Token::KeywordLet, 13..16),
(Token::Ident("z".to_string()), 17..18),
(Token::TokenAssign, 19..20),
(Token::LiteralInt("0".to_string()), 21..22),
(Token::LiteralInt(0), 21..22),
(Token::KeywordLoop, 23..27),
(Token::TokenBraceLeft, 28..29),
(Token::Ident("z".to_string()), 34..35),
Expand All @@ -31,7 +31,7 @@ fn test_lex_fibonacci() {
(Token::Ident("print".to_string()), 65..70),
(Token::Ident("z".to_string()), 71..72),
(Token::TokenBraceRight, 73..74)
]),
],
tokens
)
}
6 changes: 3 additions & 3 deletions compiler/fluxc_lexer/tests/hello_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ use pretty_assertions::assert_eq;
#[test]
fn test_lex_hello_world() {
let src = include_str!("./hello-world.flx");
let tokens = lex(src);
let tokens = lex(src).unwrap();
assert_eq!(
Ok(vec![
vec![
(Token::Ident("print".to_string()), 0..5),
(Token::LiteralStr("\"hello, world!\"".to_string()), 6..21)
]),
],
tokens
)
}
7 changes: 4 additions & 3 deletions compiler/fluxc_main/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ pub enum Mode<'i> {
}

/// Compile the target input string into memory.
pub fn compile_to_mem(_input: String) -> Result<fn() -> u32, Box<dyn Error>> {
pub fn compile_to_mem(input: String) -> Result<fn() -> u32, Box<dyn Error>> {
// 1. Parse input source
// let mut parser = fluxc_parser::ParserContext::default();
// let mut ast = parser.build(&input)?;
let tokens = fluxc_lexer::lex(input).unwrap();
let ast = fluxc_parser::parse(tokens).unwrap();
// // 2. Run AST validation on the AST
// fluxc_ast_passes::perform_ast_passes(&mut ast)?;
// // 3. Generate IR
Expand All @@ -23,6 +23,7 @@ pub fn compile_to_mem(_input: String) -> Result<fn() -> u32, Box<dyn Error>> {
// unsafe {
// code_fn = mem::transmute::<_, fn() -> u32>(pointer);
// }
println!("{:#?}", ast);
todo!()
}

Expand Down
163 changes: 150 additions & 13 deletions compiler/fluxc_parser/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,159 @@
//! The Flux parser, written using the `chumsky` library.

use chumsky::{prelude::Simple, recursive::recursive, select, Parser};
use fluxc_ast::{Expr, Literal, Node, Stmt};
use fluxc_lexer::{SpannedToken, Token};
use chumsky::{prelude::*, Stream};
use fluxc_ast::{
BinaryExpr, BinaryOp, Block, Conditional, Declaration, Expr, IfStmt, Literal, Loop, Mutability,
Node, Stmt, AST,
};
use fluxc_lexer::{Token, TokenStream};

fn parse() -> impl Parser<SpannedToken, Node<Stmt>, Error = Simple<Token>> {
fn parser() -> impl Parser<Token, AST, Error = Simple<Token>> {
// ident parser
let ident = select! {
Token::Ident(ident) => ident
}
.map_with_span(Node::new);

// literals
let literal = select! {
(Token::LiteralInt(int), _) => Literal::Int(int),
(Token::LiteralFloat(float), _) => Literal::Float(float),
(Token::LiteralStr(str), _) => Literal::String(str),
(Token::LiteralChar(c), _) => Literal::Char(c),
(Token::LiteralBool(bool), _) => Literal::Bool(bool),
Token::LiteralInt(int) => Literal::Int(int),
Token::LiteralFloat(float) => Literal::Float(f64::from_be_bytes(float)),
Token::LiteralStr(str) => Literal::String(str),
Token::LiteralChar(c) => Literal::Char(c),
Token::LiteralBool(bool) => Literal::Bool(bool),
}
.map_with_span(|literal, span| Node::new(literal, span));
.map_with_span(Node::new);

// recursive stmt declaration
let stmt = recursive::<_, Node<Stmt>, _, _, _>(|stmt| {
let block = stmt
.repeated()
.delimited_by(just(Token::TokenBraceLeft), just(Token::TokenBraceRight))
.map(|stmts| Block { stmts })
.map_with_span(Node::new);

let expr = recursive::<_, Node<Expr>, _, _, _>(|expr| {
// conditionals
let if_stmt = just(Token::KeywordIf)
.ignore_then(expr.clone())
.then(block.clone())
.map(|(condition, block)| IfStmt { block, condition: Box::new(condition) })
.map_with_span(Node::new);

let else_if_stmt = just(Token::KeywordElse).ignore_then(if_stmt.clone());
let else_stmt = just(Token::KeywordElse).ignore_then(block.clone());

let conditional = if_stmt
.then(else_if_stmt.repeated())
.then(else_stmt.or_not())
.map(|((if_stmt, else_ifs), else_stmt)| Conditional {
if_stmt,
else_ifs,
else_stmt,
})
.map_with_span(Node::new);

let loop_expr = just(Token::KeywordLoop)
.ignore_then(block.clone())
.map(|block| Loop { block, name: None })
.map_with_span(Node::new);

// binary expr
let atom =
ident.map(Expr::Ident).or(literal.map(Expr::Literal)).map_with_span(Node::new).or(
expr.clone().delimited_by(
just(Token::TokenParenthesisLeft),
just(Token::TokenParenthesisRight),
),
);

// sum operations
let op = select! {
Token::TokenPlus => BinaryOp::Plus,
Token::TokenMinus => BinaryOp::Minus,
};

let sum =
atom.clone().then(op.then(atom.clone()).repeated()).foldl(|lhs, (kind, rhs)| {
let span = lhs.span.start..rhs.span.end;
Node::new(
Expr::BinaryExpr(Node::new(
BinaryExpr { lhs: Box::new(lhs), rhs: Box::new(rhs), kind },
span.clone(),
)),
span,
)
});

let expr = recursive(|expr| {
literal.map(Expr::Literal).map_with_span(|expr, span| Node::new(expr, span))
// assignment
let op = select! {
Token::TokenAssign => BinaryOp::Assign,
Token::TokenPlusEq => BinaryOp::PlusEq,
Token::TokenMinusEq => BinaryOp::MinusEq,
Token::TokenMulEq => BinaryOp::MulEq,
Token::TokenDivEq => BinaryOp::DivEq
};
let assign = sum
.clone()
.then(op.then(sum.clone()).repeated())
.foldl(|lhs, (kind, rhs)| {
let span = lhs.span.start..rhs.span.end;
Node::new(
Expr::BinaryExpr(Node::new(
BinaryExpr {
lhs: Box::new(lhs),
rhs: Box::new(rhs),
kind,
},
span.clone(),
)),
span,
)
});

let bin_op = assign;

choice((
literal.map(Expr::Literal),
block.map(Expr::Block),
loop_expr.map(Expr::Loop),
conditional.map(Expr::Conditional),
))
.map_with_span(Node::new)
.or(bin_op)
});

let declaration_idents = ident.then_ignore(just(Token::TokenComma)).repeated().chain(ident);

let declaration = just(Token::KeywordLet)
.ignore_then(declaration_idents)
.then_ignore(just(Token::TokenAssign))
.then(expr.clone())
.map(|(idents, value)| Declaration {
explicit_ty: None,
ident: idents.into_iter().next().unwrap(),
mutability: Mutability::Immutable,
value,
})
.map_with_span(Node::new);

choice::<_, Simple<Token>>((declaration.map(Stmt::Declaration), expr.map(Stmt::Expr)))
.map_with_span(Node::new)
});

expr.map(Stmt::Expr).map_with_span(|stmt, span| Node::new(stmt, span))
stmt.repeated().then_ignore(end()).map(|stmts| AST { stmts })
}

pub fn parse(input: TokenStream) -> Result<AST, Vec<Simple<Token>>> {
// empty tokens
if input.is_empty() {
return Ok(AST { stmts: vec![] });
}

// compute eoi
let start = input.first().expect("empty stream").start();
let end = input.last().expect("empty stream").end();
let eoi = start..end;

parser().parse(Stream::from_iter(eoi, input.into_iter()))
}
4 changes: 3 additions & 1 deletion compiler/fluxc_parser/tests/fibonacci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ fn test_parse_fibonacci() {
let src = include_str!("./fibonacci.flx");
// lex
let tokens = lex(src).expect("Lexing failed!");
println!("{:?}", tokens);
// parse
let ast = parse(src, tokens).unwrap();
let ast = parse(tokens);
println!("{:?}", ast);
}

0 comments on commit d89a28e

Please sign in to comment.