One of the larger projects I did during my undergraduate studies is undoubtedly an interpreter for the simple programming language Latte.

The interpreter is written in Haskell, and the grammar is parsed into this language using the BNFC tool. The implemented functionalities are described in detail on GitHub (link below), along with numerous examples, so below is a brief overview of the implementation details.

The code of the Latte language is automatically parsed by BNFC and then passed to static type checking. The monad representing the state of the TypeChecker looks as follows:

type Env = Map.Map Var TType
data TCSt = TCSt {
    env :: Env,
    whileCnt :: Int,
    ret :: Maybe TType
}

The types of variables are stored in the environment env. The purpose of whileCnt is to count the encountered loops, which ensures correct handling of the break and continue statements, while ret holds the expected return type of the current function (it also prevents the use of return outside of a function).

After a successful static analysis of the code, the execution follows. The monad representing the state of the Interpreter looks as follows:

type Env = Map.Map Var Loc
type Store = Map.Map Loc Value
type FStore = Map.Map Loc Fun
data St = St {
    env :: Env,
    store :: Store,
    funcs :: FStore
}

The environment env maps variable and function names to memory locations, store maps variable locations to their corresponding values, and funcs maps function locations to their corresponding definitions.

Source code