Workshop: Walkthrough the prepared codebase

.
├── ast.rs // The language Abstract syntax tree.
├── codegen
│   ├── expressions.rs
│   ├── ifelse_stmt.rs
│   ├── let_stmt.rs
│   └── return_stmt.rs
├── codegen.rs // Glue code for the codegen methods.
├── grammar.lalrpop // LALRPOP grammar for parsing
├── main.rs // CLI and MLIR Context creation
└── util.rs // Code to translate MLIR to LLVM and link the binary

The workshop project already contains the code to handle the following:

  • Lexer and parser
  • CLI
  • The language AST
  • Translating to LLVM bytecode and linking the binary.

Thus what's missing is implementing the methods that "compile" the code, a.k.a emit the MLIR operations. They are located under the codegen/ folder.

The AST

The language AST is quite simple, it consists of the following:

#![allow(unused)]
fn main() {
/// The possible expressions, usually on the right hand side of an assignment
/// let x = <expr> ;
#[derive(Debug, Clone)]
pub enum Expr {
    Number(i64),
    Call { target: String, args: Vec<Expr> },
    Variable(String),
    Op(Box<Expr>, Opcode, Box<Expr>),
}

#[derive(Debug, Clone)]
pub enum Opcode {
    Mul,
    Div,
    Add,
    Sub,
    Eq,
    Neq,
}

// A statement, separated by ;
#[derive(Debug, Clone)]
pub enum Statement {
    Let(LetStmt),
    If(IfStmt),
    Return(ReturnStmt),
}

/// The let statement, it binds a value from an expression to the given variable.
#[derive(Debug, Clone)]
pub struct LetStmt {
    pub variable: String,
    pub expr: Expr,
}

/// An if with an optional else statement, depending on whether the condition evaluates to true,
/// take one or another block.
#[derive(Debug, Clone)]
pub struct IfStmt {
    pub cond: Expr,
    pub then: Block,
    pub r#else: Option<Block>,
}

/// The return statement of a function
#[derive(Debug, Clone)]
pub struct ReturnStmt {
    pub expr: Expr,
}

/// A block is a series of statements, used as the function body and if else blocks.
#[derive(Debug, Clone)]
pub struct Block {
    pub stmts: Vec<Statement>,
}

/// Describes a function, with the arguments.
/// Note: in this simple language functions always return a i64.
#[derive(Debug, Clone)]
pub struct Function {
    pub name: String,
    pub args: Vec<String>,
    pub body: Block,
}

/// The whole program, simply a list of functions.
/// The function named "main" will be the entrypoint.
#[derive(Debug, Clone)]
pub struct Program {
    pub functions: Vec<Function>,
}

}