initial commit
This commit is contained in:
commit
43df8c4b0a
9 changed files with 596 additions and 0 deletions
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "kronec"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.3.0", features = ["derive"] }
|
||||
cranelift = "0.96.3"
|
||||
cranelift-jit = "0.96.3"
|
||||
cranelift-module = "0.96.3"
|
||||
cranelift-native = "0.96.3"
|
||||
lazy_static = "1.4.0"
|
||||
pest = "2.6.0"
|
||||
pest_derive = "2.6.0"
|
||||
19
src/ast/expr.rs
Normal file
19
src/ast/expr.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use crate::ast::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Expr {
|
||||
BinaryExpression(Box<Expr>, BinaryOperator, Box<Expr>),
|
||||
Identifier(Identifier),
|
||||
IntegerLiteral(i64),
|
||||
FloatLiteral(f64),
|
||||
StringLiteral(String),
|
||||
Call(Box<Call>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum BinaryOperator {
|
||||
Add,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
}
|
||||
71
src/ast/mod.rs
Normal file
71
src/ast/mod.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
pub mod expr;
|
||||
pub mod typ;
|
||||
|
||||
pub use crate::ast::expr::{BinaryOperator, Expr};
|
||||
pub use crate::ast::typ::*;
|
||||
|
||||
// XXX: Is this enum actually useful? Is 3:30 AM btw
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Ast {
|
||||
FunctionDefinition(FunctionDefinition),
|
||||
Expr(Expr),
|
||||
Module(Vec<Ast>),
|
||||
Block(Block),
|
||||
Statement(Statement),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct FunctionDefinition {
|
||||
pub name: Identifier,
|
||||
pub parameters: Vec<Parameter>,
|
||||
pub return_type: Option<Type>,
|
||||
pub body: Box<Block>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Block {
|
||||
pub statements: Vec<Statement>,
|
||||
pub value: Option<Expr>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Statement {
|
||||
AssignStatement(Identifier, Expr),
|
||||
ReturnStatement(Option<Expr>),
|
||||
CallStatement(Call),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Call {
|
||||
pub callee: Expr,
|
||||
pub args: Vec<Expr>,
|
||||
}
|
||||
|
||||
pub type Identifier = String;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Parameter {
|
||||
pub name: Identifier,
|
||||
pub typ: Type,
|
||||
}
|
||||
|
||||
impl Ast {
|
||||
/// Type checks the AST and add missing return types.
|
||||
pub fn check_return_types(&mut self) -> Result<(), TypeError> {
|
||||
match self {
|
||||
Ast::Module(defs) => {
|
||||
for def in defs {
|
||||
if let Ast::FunctionDefinition { .. } = def {
|
||||
def.check_return_types()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ast::FunctionDefinition(func) => {
|
||||
let typ = func.typ(&mut TypeContext::default())?;
|
||||
func.return_type = Some(typ.clone());
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
158
src/ast/typ.rs
Normal file
158
src/ast/typ.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::ast::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Type {
|
||||
Int,
|
||||
Float,
|
||||
Unit,
|
||||
Str,
|
||||
Custom(Identifier),
|
||||
}
|
||||
|
||||
impl From<&str> for Type {
|
||||
fn from(value: &str) -> Self {
|
||||
match value {
|
||||
"int" => Type::Int,
|
||||
"float" => Type::Float,
|
||||
_ => Type::Custom(Identifier::from(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TypeError {
|
||||
InvalidBinaryOperator {
|
||||
operator: BinaryOperator,
|
||||
lht: Type,
|
||||
rht: Type,
|
||||
},
|
||||
BlockTypeDoesNotMatchFunctionType {
|
||||
function_name: String,
|
||||
function_type: Type,
|
||||
block_type: Type,
|
||||
},
|
||||
ReturnTypeDoesNotMatchFunctionType {
|
||||
function_name: String,
|
||||
function_type: Type,
|
||||
ret_type: Type,
|
||||
},
|
||||
UnknownIdentifier {
|
||||
identifier: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct TypeContext {
|
||||
pub function: Option<Identifier>,
|
||||
pub variables: HashMap<Identifier, Type>,
|
||||
}
|
||||
|
||||
/// Trait for nodes which have a deducible type.
|
||||
pub trait Typ {
|
||||
/// Try to resolve the type of the node.
|
||||
fn typ(&self, ctx: &mut TypeContext) -> Result<Type, TypeError>;
|
||||
}
|
||||
|
||||
impl Typ for FunctionDefinition {
|
||||
fn typ(&self, ctx: &mut TypeContext) -> Result<Type, TypeError> {
|
||||
let func = self;
|
||||
|
||||
let mut ctx = TypeContext {
|
||||
function: Some(func.name.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
for param in &func.parameters {
|
||||
ctx.variables.insert(param.name.clone(), param.typ.clone());
|
||||
}
|
||||
|
||||
let body_type = &func.body.typ(&mut ctx)?;
|
||||
|
||||
// If the return type is not specified, it is unit.
|
||||
let func_return_type = match &func.return_type {
|
||||
Some(typ) => typ,
|
||||
None => &Type::Unit,
|
||||
};
|
||||
|
||||
// Check coherence with the body's type.
|
||||
if *func_return_type != *body_type {
|
||||
return Err(TypeError::BlockTypeDoesNotMatchFunctionType {
|
||||
function_name: func.name.clone(),
|
||||
function_type: func_return_type.clone(),
|
||||
block_type: body_type.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
// Check coherence with return statements.
|
||||
for statement in &func.body.statements {
|
||||
if let Statement::ReturnStatement(value) = statement {
|
||||
let ret_type = match value {
|
||||
Some(expr) => expr.typ(&mut ctx)?,
|
||||
None => Type::Unit,
|
||||
};
|
||||
if ret_type != *func_return_type {
|
||||
return Err(TypeError::ReturnTypeDoesNotMatchFunctionType {
|
||||
function_name: func.name.clone(),
|
||||
function_type: func_return_type.clone(),
|
||||
ret_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(func_return_type.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Typ for Block {
|
||||
fn typ(&self, ctx: &mut TypeContext) -> Result<Type, TypeError> {
|
||||
// Check if there is an expression at the end of the block.
|
||||
if let Some(expr) = &self.value {
|
||||
expr.typ(ctx)
|
||||
} else {
|
||||
Ok(Type::Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Typ for Expr {
|
||||
fn typ(&self, ctx: &mut TypeContext) -> Result<Type, TypeError> {
|
||||
match self {
|
||||
Expr::Identifier(identifier) => {
|
||||
if let Some(typ) = ctx.variables.get(identifier) {
|
||||
Ok(typ.clone())
|
||||
} else {
|
||||
Err(TypeError::UnknownIdentifier {
|
||||
identifier: identifier.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Expr::IntegerLiteral(_) => Ok(Type::Int),
|
||||
Expr::FloatLiteral(_) => Ok(Type::Float),
|
||||
Expr::BinaryExpression(lhs, op, rhs) => match op {
|
||||
BinaryOperator::Add
|
||||
| BinaryOperator::Sub
|
||||
| BinaryOperator::Mul
|
||||
| BinaryOperator::Div => {
|
||||
let left_type = &lhs.typ(ctx)?;
|
||||
let right_type = &rhs.typ(ctx)?;
|
||||
match (left_type, right_type) {
|
||||
(Type::Int, Type::Int) => Ok(Type::Int),
|
||||
(Type::Float, Type::Int | Type::Float) => Ok(Type::Float),
|
||||
(Type::Int, Type::Float) => Ok(Type::Float),
|
||||
(_, _) => Err(TypeError::InvalidBinaryOperator {
|
||||
operator: op.clone(),
|
||||
lht: left_type.clone(),
|
||||
rht: right_type.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
},
|
||||
Expr::StringLiteral(_) => Ok(Type::Str),
|
||||
Expr::Call(call) => {
|
||||
todo!("resolve call type using ctx");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub mod ast;
|
||||
62
src/main.rs
Normal file
62
src/main.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
mod ast;
|
||||
mod parsing;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::fs;
|
||||
|
||||
/// Experimental compiler for krone
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author = "Romain P. <rpqt@rpqt.fr>")]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
Parse {
|
||||
/// Path to the source file
|
||||
file: String,
|
||||
|
||||
/// Dump the AST to stdout
|
||||
#[arg(long)]
|
||||
dump_ast: bool,
|
||||
|
||||
/// Add missing return types in the AST
|
||||
#[arg(long)]
|
||||
complete_ast: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match &cli.command {
|
||||
Commands::Parse {
|
||||
file,
|
||||
dump_ast,
|
||||
complete_ast,
|
||||
} => {
|
||||
let source = fs::read_to_string(&file).expect("could not read the source file");
|
||||
let mut ast = match parsing::parse(&source) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => panic!("Parsing error: {:#?}", e),
|
||||
};
|
||||
|
||||
if *complete_ast {
|
||||
if let Err(e) = ast.check_return_types() {
|
||||
eprintln!("{:#?}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if *dump_ast {
|
||||
println!("{:#?}", &ast);
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Parsing OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
52
src/parsing/grammar.pest
Normal file
52
src/parsing/grammar.pest
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// This file is just a little test of pest.rs
|
||||
|
||||
source_file = { SOI ~ definition* ~ EOI }
|
||||
|
||||
statement = { assign_statement | return_statement | call_statement }
|
||||
assign_statement = { "set" ~ ident ~ "=" ~ expr ~ ";" }
|
||||
return_statement = { "return" ~ expr? ~ ";" }
|
||||
call_statement = { call ~ ";" }
|
||||
|
||||
// Function calls
|
||||
call = { ident ~ "(" ~ args ~ ")" }
|
||||
args = { (expr ~ ",")* ~ expr? }
|
||||
|
||||
definition = { func_def }
|
||||
|
||||
func_def = { "fn" ~ ident ~ "(" ~ parameters ~ ")" ~ typ? ~ block }
|
||||
parameters = {
|
||||
(parameter ~ ",")* ~ (parameter)?
|
||||
}
|
||||
parameter = { ident ~ ":" ~ typ }
|
||||
|
||||
block = { "{" ~ statement* ~ expr? ~ "}" }
|
||||
|
||||
// Operators
|
||||
infix = _{ add | subtract | multiply | divide }
|
||||
add = { "+" }
|
||||
subtract = { "-" }
|
||||
multiply = { "*" }
|
||||
divide = { "/" }
|
||||
|
||||
prefix = _{ not }
|
||||
not = { "!" }
|
||||
|
||||
expr = { prefix? ~ atom ~ (infix ~ prefix? ~ atom)* }
|
||||
atom = _{ call | ident | literal | "(" ~ expr ~ ")" }
|
||||
|
||||
ident = @{ (ASCII_ALPHA | "_")+ }
|
||||
typ = _{ ident }
|
||||
|
||||
// Literals
|
||||
literal = _{ float_literal | integer_literal | string_literal }
|
||||
string_literal = ${ "\"" ~ string_content ~ "\"" }
|
||||
string_content = @{ char* }
|
||||
char = {
|
||||
!("\"" | "\\") ~ ANY
|
||||
| "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
|
||||
| "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
|
||||
}
|
||||
integer_literal = @{ ASCII_DIGIT+ }
|
||||
float_literal = @{ ("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) ~ "." ~ ASCII_DIGIT* }
|
||||
|
||||
WHITESPACE = _{ " " | "\n" | "\t" }
|
||||
38
src/parsing/mod.rs
Normal file
38
src/parsing/mod.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
pub mod pest;
|
||||
|
||||
pub use self::pest::parse;
|
||||
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_addition_function() {
|
||||
use crate::ast::*;
|
||||
use crate::parsing::pest::parse;
|
||||
|
||||
let source = "fn add(a: int, b: int) int { a + b }";
|
||||
|
||||
let ast = Ast::FunctionDefinition(FunctionDefinition {
|
||||
name: Identifier::from("add"),
|
||||
parameters: vec![
|
||||
Parameter {
|
||||
name: Identifier::from("a"),
|
||||
typ: Type::Int,
|
||||
},
|
||||
Parameter {
|
||||
name: Identifier::from("b"),
|
||||
typ: Type::Int,
|
||||
},
|
||||
],
|
||||
return_type: Some(Type::Int),
|
||||
body: Box::new(Block {
|
||||
statements: vec![],
|
||||
value: Some(Expr::BinaryExpression(
|
||||
Box::new(Expr::Identifier(Identifier::from("a"))),
|
||||
BinaryOperator::Add,
|
||||
Box::new(Expr::Identifier(Identifier::from("b"))),
|
||||
)),
|
||||
}),
|
||||
});
|
||||
|
||||
assert_eq!(parse(source).unwrap(), Ast::Module(vec![ast]));
|
||||
}
|
||||
}
|
||||
181
src/parsing/pest.rs
Normal file
181
src/parsing/pest.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
use lazy_static;
|
||||
use pest::error::Error;
|
||||
use pest::iterators::Pair;
|
||||
use pest::pratt_parser::PrattParser;
|
||||
use pest::Parser;
|
||||
|
||||
use crate::ast::*;
|
||||
|
||||
#[derive(pest_derive::Parser)]
|
||||
#[grammar = "parsing/grammar.pest"]
|
||||
struct KrParser;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref PRATT_PARSER: PrattParser<Rule> = {
|
||||
use pest::pratt_parser::{Assoc::*, Op};
|
||||
use Rule::*;
|
||||
|
||||
// Precedence is defined lowest to highest
|
||||
PrattParser::new()
|
||||
// Addition and subtract have equal precedence
|
||||
.op(Op::infix(add, Left) | Op::infix(subtract, Left))
|
||||
.op(Op::infix(multiply, Left) | Op::infix(divide, Left))
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parse(source: &str) -> Result<Ast, Error<Rule>> {
|
||||
let mut definitions: Vec<Ast> = vec![];
|
||||
|
||||
let pairs = KrParser::parse(Rule::source_file, source)?;
|
||||
for pair in pairs {
|
||||
match pair.as_rule() {
|
||||
Rule::source_file => {
|
||||
let pairs = pair.into_inner();
|
||||
for pair in pairs {
|
||||
match pair.as_rule() {
|
||||
Rule::definition => {
|
||||
let definition = parse_definition(pair.into_inner().next().unwrap());
|
||||
definitions.push(definition);
|
||||
}
|
||||
Rule::EOI => {}
|
||||
_ => panic!("unexpected rule in source_file: {:?}", pair.as_rule()),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => eprintln!("unexpected top-level rule {:?}", pair.as_rule()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Ast::Module(definitions))
|
||||
}
|
||||
|
||||
fn parse_block(pair: Pair<Rule>) -> Block {
|
||||
let mut statements = vec![];
|
||||
let mut value = None;
|
||||
|
||||
for pair in pair.into_inner() {
|
||||
match pair.as_rule() {
|
||||
Rule::statement => statements.push(parse_statement(pair)),
|
||||
Rule::expr => value = Some(parse_expression(pair)),
|
||||
_ => panic!("unexpected rule {:?} in block", pair.as_rule()),
|
||||
}
|
||||
}
|
||||
|
||||
Block { statements, value }
|
||||
}
|
||||
|
||||
fn parse_statement(pair: Pair<Rule>) -> Statement {
|
||||
let pair = pair.into_inner().next().unwrap();
|
||||
match pair.as_rule() {
|
||||
Rule::assign_statement => {
|
||||
let mut pairs = pair.into_inner();
|
||||
let identifier = pairs.next().unwrap().as_str().to_string();
|
||||
let expr = parse_expression(pairs.next().unwrap());
|
||||
Statement::AssignStatement(identifier, expr)
|
||||
}
|
||||
Rule::return_statement => {
|
||||
let expr = if let Some(pair) = pair.into_inner().next() {
|
||||
Some(parse_expression(pair))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Statement::ReturnStatement(expr)
|
||||
}
|
||||
Rule::call_statement => {
|
||||
let call = parse_call(pair.into_inner().next().unwrap());
|
||||
Statement::CallStatement(call)
|
||||
}
|
||||
_ => unreachable!("unexpected rule '{:?}' in parse_statement", pair.as_rule()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_call(pair: Pair<Rule>) -> Call {
|
||||
let mut pairs = pair.into_inner();
|
||||
// TODO: support calls on more than identifiers (needs grammar change)
|
||||
let callee = Expr::Identifier(pairs.next().unwrap().as_str().to_string());
|
||||
let args: Vec<Expr> = pairs
|
||||
.next()
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.map(parse_expression)
|
||||
.collect();
|
||||
Call { callee, args }
|
||||
}
|
||||
|
||||
fn parse_expression(pair: Pair<Rule>) -> Expr {
|
||||
let pairs = pair.into_inner();
|
||||
PRATT_PARSER
|
||||
.map_primary(|primary| match primary.as_rule() {
|
||||
Rule::integer_literal => Expr::IntegerLiteral(primary.as_str().parse().unwrap()),
|
||||
Rule::float_literal => Expr::FloatLiteral(primary.as_str().parse().unwrap()),
|
||||
Rule::string_literal => Expr::StringLiteral(
|
||||
primary
|
||||
.into_inner()
|
||||
.next()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.parse()
|
||||
.unwrap(),
|
||||
),
|
||||
Rule::ident => Expr::Identifier(primary.as_str().to_string()),
|
||||
Rule::expr => parse_expression(primary),
|
||||
Rule::call => Expr::Call(Box::new(parse_call(primary))),
|
||||
_ => unreachable!(
|
||||
"Unexpected rule '{:?}' in primary expression",
|
||||
primary.as_rule()
|
||||
),
|
||||
})
|
||||
.map_infix(|lhs, op, rhs| {
|
||||
let operator = match op.as_rule() {
|
||||
Rule::add => BinaryOperator::Add,
|
||||
Rule::subtract => BinaryOperator::Sub,
|
||||
Rule::multiply => BinaryOperator::Mul,
|
||||
Rule::divide => BinaryOperator::Div,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Expr::BinaryExpression(Box::new(lhs), operator, Box::new(rhs))
|
||||
})
|
||||
.parse(pairs)
|
||||
}
|
||||
|
||||
fn parse_parameter(pair: Pair<Rule>) -> Parameter {
|
||||
assert!(pair.as_rule() == Rule::parameter);
|
||||
let mut pair = pair.into_inner();
|
||||
let name: String = pair.next().unwrap().as_str().to_string();
|
||||
let typ = Type::from(pair.next().unwrap().as_str());
|
||||
Parameter { name, typ }
|
||||
}
|
||||
|
||||
fn parse_definition(pair: Pair<Rule>) -> Ast {
|
||||
match pair.as_rule() {
|
||||
Rule::func_def => {
|
||||
let mut pairs = pair.into_inner();
|
||||
let name = pairs.next().unwrap().as_str().to_string();
|
||||
let parameters: Vec<Parameter> = pairs
|
||||
.next()
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.map(parse_parameter)
|
||||
.collect();
|
||||
let pair = pairs.next().unwrap();
|
||||
// Before the block there is an optional return type
|
||||
let (return_type, pair) = match pair.as_rule() {
|
||||
Rule::ident => (Some(Type::from(pair.as_str())), pairs.next().unwrap()),
|
||||
Rule::block => (None, pair),
|
||||
_ => unreachable!(
|
||||
"Unexpected rule '{:?}' in function definition, expected return type or block",
|
||||
pair.as_rule()
|
||||
),
|
||||
};
|
||||
let body = parse_block(pair);
|
||||
let body = Box::new(body);
|
||||
Ast::FunctionDefinition(FunctionDefinition {
|
||||
name,
|
||||
parameters,
|
||||
return_type,
|
||||
body,
|
||||
})
|
||||
}
|
||||
_ => panic!("unexpected node for definition: {:?}", pair.as_rule()),
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue