Вы сказали:
here is my task 

# EPIC++ Programming Language (max. 100 points)

In this task, you will have to write the compiler for EPIC++ programming language. Unlike the interpreter from one of the previous homeworks, now the language will be compiled using LLVM and will contain some low-level features like memory allocation, pointers and static typing.

Before proceeding, please read the "General Information" section (and all of its subsections) carefully!

Good luck ;)

## General Information

### Operating System Support

To solve the task, you will need a UNIX-like operating system (e.g. GNU/Linux, FreeBSD, macOS). Windows is **not** a UNIX-like operating system, so the code will not work under Windows. If you use Windows, you have three different options:
* Install WSL (Windows Subsystem for Linux) and use it for solving the task.
* Install any Linux distribution into a virtual machine and use it for solving the task.
* (For advanced users only!) Patch runtime.py so that it can succesfully find calloc() and free() functions from the Standard C Library. Everything else should work correctly.
  - If you have managed to patch runtime.py successfully, please share how you did it, it may help other students to solve the task using Windows.

### Requirements

You must have Python with llvmlite installed. Also you must have ANTLR installed. To install it with pip, you may run

sh
$ pip install antlr4-python3-runtime antlr4-tools llvmlite


### Structure

The task directory has the following structure:
- codegen.py is the file where you will write the compiler which transforms source code into LLVM.
- EpicPP.g4 is the grammar in EPIC++ language. For convenience, the grammar is already pre-written for you.
- parseutil.py contains some functions that you may find useful for implementation. Please read this file before writing any code.
- README.md is the file you are reading right now.
- runner.py is the main module of your compiler.
- runtime.py is the module which injects system functions.
- test.py is the utility to test your compiler.
- tests/ is the directory that contains tests. The tests are subdivided into nine subtasks. Each subtask contains one or more tests. Each test consists of a .epp file (program you need to compile) and an .out file (expected output for the program). For example, the 4th test of the 3rd subtask is located at tests/3/4.epp and tests/3/4.out.

Please, **do not** modify any of the files except codegen.py!

### Submitting

You must submit only one file into the testing system, codegen.py. Do not try to submit an archive or anything except codegen.py, it won't be accepted.

### Building

Before doing anything else, you should build the grammar. To do this, run

sh
$ antlr4 EpicPP.g4 -Dlanguage=Python3 -visitor


### Running

To run your compiler locally on some program some/my/prog.epp, use

sh
$ python3 runner.py some/my/prog.epp


The runner will compile and execute your program (using your codegen.py, of course). Also, it will produce the following files in the same directory where the .epp file is located:
* .ll file is the LLVM bitcode produced by your compiler.
* .bc file is the LLVM bitcode produced by your compiler and converted into binary form.
* .opt.bc file is the LLVM bitcode after applying optimizations (in binary form).

To inspect .bc files, you must:
* Install LLVM tools. For Debian or Ubuntu, it's sudo apt install llvm. For other distributions or operating systems, search the Internet on how to do it.
* Run llvm-dis prog.bc -o -, where prog.bc is your .bc file. The resulting bitcode is printed to the terminal.

Note that inspecting .bc files is not necessary neither for debugging, nor for completing the task. But they are still provided because you may want to see the optimized code out of curiosity.

### Testing

You may run all the tests at once. To do it, use

sh
$ python3 test.py


To run only the tests for one subtask (for example, for subtask 6), run:

sh
$ python3 test.py 6


The test.py script exits with exitcode 0 if all the tests pass, or with exitcode 1 if any of them fails.

### Subtasks

The task is subdivided into nine subtasks of increasing difficulty. For each subtask, you must implement some subset of the language. To implement the entire language, you must complete all the nine subtasks.

The description of the subtasks, as well as the features you must implement for them, is given below. Each subtask is worth some amount of points.

To pass the i-th subtask, you must pass all the tests for this subtask and all the previous subtasks. So, if any of the tests for the i-th subtask fails, then the i-th subtask, as well as all the subsequent subtasks, are considered failed.

If you pass a subtask, you receive all the points for this subtask. The final score is the sum of points over all the passed subtasks. For full score you must pass all the tests from all the subtasks, of course.

### Error Handling

You may assume that all the programs are correct and don't contain any errors. You may also assume that programs don't do any invalid operations (i.e. integer division by zero, array overflows, dereferencing invalid pointers, etc.)

Thus, you don't have to handle any errors in your compiler and do anything you want when you encounter an invalid program.

All the tests are considered valid programs. If you suspect that they are not valid, you may write in Discord. Before claiming that the tests are wrong, please double-check carefully that this is indeed the case.

For simplicity of debugging, there is already a ErrorHandler in your codegen.py, which catches syntax errors. You may also choose to write assert's all over your code for invalid conditions (e.g. type mismatch). Such assert's are not required, but can be very helpful for you to debug your code.

## Useful Functions

In your LLVM module, there are several predefined functions. They are implemented outside of your LLVM module (either in Python or taken from the C Standard Library). They are declared in runtime.py and injected into your module in codegen.py, in IRBuilder's constructor. The names of these functions start from $, to distinguish them from functions defined in your module. The functions are as follows:
* $sys_calloc(nmemb, size): allocates an array consisting of nmemb elements. Each element is size bytes long. Thus, it allocates nmemb*size bytes. Returns a pointer to the beginning of the array. Zeroes the allocated memory.
* $sys_free(ptr): frees the memory allocated by calloc(). ptr must point to the beginning of the allocated memory!
* $sys_write_i64(val), $sys_write_f64(val), $sys_write_char(val), $sys_write_bool(val), $sys_write_str(val): prints a value of type i64, f64, char, bool or *char, respectively.

# Subtasks

## Subtask 1 (15 points)

The program contains only function main, taking no arguments and returning an i64. No other functions are present. There are some statements inside the function, as described below. The last statement is always return 0;, and there are no other return statements.

All the other statements are either _empty statements_ consisting only of ; (such statements do nothing and can be ignored), or _expression statements_ containing an expression.

Three types are supported: i64, i32 and char (signed 64-bit, 32-bit and 8-bit integers, respectively).

The following features are supported in expressions:
* Integer literals, decimal and hexadecimal. All the integer literals are of type i64.
* Character literals (e.g. ' ', '\n', '\x42'), of type char.
* Casts between integer types with @ operator. E.g. 42 @ i32 casts 42 from i64 to i32. Casts from the type to itself are allowed.
  - _Hint_: for widening casts, use sext instruction, for narrowing casts, use trunc instruction.
* Unary operators: +, -. Such operators don't change the type.
* Binary operators: +, -, *, /, %. Such operators take two expressions of the same type and produce the result of the same type.
* Brackets (they behave like in math)
* Calls to a special write() function. This function returns _nothing_, so it appears only as the top-level node of an expression. write() takes an arbitrary number of arguments of any of the types i32, i64, char and prints its arguments one by one (without spaces between them).
  - _Hint_: to write i64 and i32, use $sys_write_i64 (for i32, cast it to i64 first), to write char, use $sys_write_char.

## Subtask 2 (10 points)

Support new kinds of statements:
* Block statements, denoted as {} with some other statements inside the braces. Statements inside the block are executed one-by-one. Also, each block starts a new _scope_ for variables. Function body is a top-level scope for variables.
* Variable declarations, e.g. var x: i64 = 42; or var x: i64;. When the variable is declared without initialization, its value is undefined. A variable is bound to the scope where it's declared and is un-declared at the end of the scope. When the outer scope declares some variable (e.g. x) and the inner scope declares its own variable x, then the outer x is temporarily shadowed by the inner one, and after the inner scope finishes, the outer x is used. In other words, the behavior is the same as you would get from most compiled languages (but not the same as Python, which has no inner scopes!). For more clarity, see the code below:

  
var x: i64 = 5;
  write(x, '\n');  // prints `5`
  {
      // the inner scope starts
      write(x, '\n');  // no `x` in the inner scope for now, so `5`
      var x: i64 = 6;  // inner `x` is declared 
      write(x, '\n');  // inner `x` is used, so `6`
  }
  // after the end of the scope, there is no more inner `x`, and the outer one is used
  write(x, '\n');  // prints `5`


  - _Hint_: do not try to deal with SSA directly when implementing the variables, just use alloca and hope the optimizer works fine. 
* Variable assignments, e.g. x = 5; assigns the value 5 to the variable x.

Of course, you must support using variables in expressions instead of values. For example, if a = 2 and b = 3 then a + b should just give 5.

## Subtask 3 (10 points)

Now, you must support other functions, not only main. Still, main is guaranteed to be present and is guaranteed to take no arguments and return an i64.

The functions may go in any order, so it's recommended first to iterate over all the functions and declare them, and then parse what's inside of the functions and build LLVM IR for them.

Each function may take zero or more arguments and return a value. Note that function arguments are variables and can be reassigned, so alloca them and copy their values before using. When a function is called, all its arguments are copied by value, so reassigning an argument inside the function doesn't change its value outside.

You must also support _return statements_, like return 42;, to return values from functions. Return statements are always the last meaningful statements in the functions, but may be nested deep inside many levels of blocks.

Of course, you must also support function calls from the expressions. To avoid confusion with the special pseudo-function write() mentioned above, no declared function can be named write.

Also, you must support a special none type and a special literal none of type none. Their use is limited to the following cases:
* Type none can be used as a return type if a function returns no value. In this case, return can be omitted from the function at all, or can be written as either return; or return none;.
* Literal none can be used as a topmost expression in the statement, so none; is valid and does nothing.
* If the function returns none then return EXPR; is valid if EXPR is an expression of type none. Note that write() technically also returns none, so return write(42, '\n'); is valid if the function returns none. See tests/3/5.epp for more information.

Note that no variable and no function argument can be of type none. So, none is pretty similar to void type in C or C++.

## Subtask 4 (5 points)

Support boolean type and boolean expressions:
* Support a new boolean type bool and its literals (true and false).
* Support unary boolean NOT operation (!).
* Support binary AND and OR boolean operations (& and |, respectively). Note that, unlike in most languages, you don't have to implement short-circuting, so the expression func1() & func2() must always evaluate **both** func1() and func2(), regardless of their return values.
* Support writing booleans via write(). Use $sys_write_bool to implement it.
* Support comparisons between integers (<=, >=, <, >, ==, !=). Comparison is a binary operator that takes two integers of the same type and returns a bool.
* Support == and != operators which take two bool's, check them for (in)equality, and return a bool.

## Subtask 5 (15 points)

Now, it's time to support some control flow. Previously, all the functions had a single LLVM block. Now, you should be able to create functions consisting of many LLVM blocks.

Support if's (both with and without else). Condition of if's are always of type bool. For example:

if x <= 5 then {
  write('1', '\n');
} else {
  x = x - 1;
  write('2', '\n');
}
if y > 4 then return 42;


You may encounter return's inside if branches, but it's guaranteed that return is located in the end of the branch. A return can be nested in multiple blocks, though.

Support loops (both for and while). The condition of while, like in if, is always of type bool. The for loop takes a variable name and two boundaries of the same integer type, creates a new scope, declares a loop variable inside the new scope and iterates it from one boundary to another (the right boundary is not included). The type of the for loop variable is the same as the type of its boundaries. For example:

while x <= 5 do {
  write(x, '\n');
  x = x + 1;
}
var i = 42;
// the following loop prints integers 1, 2, 3, 4, 5, 6, 7, 8 and 9
for i in 1 .. 10 do {
  // as the boundaries are of type `i64`, the loop variable of type `i64`.
  write(i, '\n');
}
write(i, '\n'); // prints `42`, as the loop variable lives in its own scope
                // and is undeclared after the loop ends.


Like in if, you may encounter return's in the loops, but only at the end of the loop.

Also, support break and continue in the loops. They should behave just like they do in Python.

## Subtask 6 (5 points)

Support floating point arithmetic:
* Support two new floating point types, f32 and f64.
* Support floating point literals (they always have type f64)
* Support casts between floating point types. _Hint_: use fpext for widening conversion and fptrunc for narrowing conversion.
* Support integer-to-float and float-to-integer casts via @ operator. _Hint_: use fptosi and sitofp and remember that all our integers are signed.
* Support arithmetic operations between floats of the same type: unary + and -, binary +, -, * and /, comparsions (<=, >=, <, >, ==, !=).
* Support printing floats via write(). Use $sys_write_f64 function for f64, and $sys_write_f64 plus cast for f32.

## Subtask 7 (15 points)

Support pointers:
* Support pointer types (*T is a pointer for some type T). Pointers can point to other pointers: for example, **i64 is a pointer to a pointer to an i64.
* Support null literal, meaning a null pointer. The special thing about null is that it should be implicitly castable to any other pointer type. For example var f: *i64 = null; is a valid declaration. Also, passing null to a function in place of any pointer argument is allowed.
* Support allocation and deallocation. new T expression must allocate a value of type T and return a pointer to it. delete ptr; statement must free the memory allocated by new. Some hints:
  - To support new T, you must use $sys_calloc function. Use 1 (number of elements) as the first argument, and sizeof(T) as the second argument. See below on how to calculate sizeof of any LLVM type T.
  - To support delete, just pass the pointer to delete into $sys_free.
  - Note that both $sys_calloc and $sys_free accept ir.PointerType(ir.IntType(8)) (i.e. *char) as arguments and return values. To cast them into the correct pointer type, you might need to do bitcast.
- Support unary * operator to dereference a pointer and unary & operator to create a pointer to a given value. Some hints:
  * Earlier, we supported only assignment to variables. From here, you have a concept of _lvalue_ and _rvalue_ expressions. Rvalue expressions may appear only to the right side of assignments, and they cannot be assigned to (e.g. literals, values returned from functions, results of +, - and other operators, etc.). Lvalue expressions may appear to both sides of assignments, and they can be assigned to (e.g. variables, function arguments, pointers dereferenced by *, etc.) Thus, *x = 42;, **x = 42;, **x = *y; and *some_fn(a, &b, &c) = 42; are valid assignments.
  * Note that * (and most other operators) applies to both lvalue and rvalue expressions, and & applies only to lvalue expressions.
  * In LLVM, once you load() a value from a pointer, you cannot take its address back. So, it's recommended to keep values of lvalue expressions as pointers internally and cast them to values only when needed.
- Support comparison between pointers via == and !=. You must also support comparison with null, of course.

The following snippet can be useful for you in order to calculate sizeof of any type T:

python
    def sizeof(self, ll: Any):
        return self.__builder.ptrtoint(
            self.__builder.gep(ir.Constant(ir.PointerType(ll), 'null'), [ir.Constant(ir.IntType(64), 1)]), ir.IntType(64))


For now, you may even hard-code sizeof's of all the types, but this snippet can be still useful to calculate sizeof of a user-defined struct when you reach the last subtask.

## Subtask 8 (10 points)

Support arrays. Arrays have the same type as pointers. A pointer points to the 0-th element of the array. Technically, all pointers allocated by new T are treated the same as one-element arrays. But, for full-featured array support, you need a bit more things:
* Support indexing via []. Note that indexing returns an lvalue expression, so if a has type **i64, then a[1] = new i64; and even a[1][2] = 42 + a[2][3]; must work properly. Also note that indices can be of any integer type, do sext properly if you need to convert them to i64.
* Support array allocation via new[x] T which allocates an array containing x elements of type T. It must return a value of type *T. Again, use $sys_calloc, but pass x instead of 1 as the first argument. Also note that array sizes can be of any integer type (but they are always non-negative), do sext for conversion.

Also, support strings. Strings are arrays of type *char. They are NUL-terminated, like in C. It means that any string must always end with an '\x00' character, which marks the end of the string.

Mostly, you can do the same things with strings as with ordinary arrays, but you have to support a few more things:
* Support write() for strings (of type *char). Use $sys_write_str to implement it.
* Support string literals, of type *char. Do not forget to add terminating '\x00' to the end of your string literal!

Supporting string literals is a bit more complex than other literals. You need to be able to index string literals and put them into *char variables. So, they must be global constants of type ir.ArrayType(ir.IntType(8), l), where l is the length of your string (including terminating '\x00'). To create a global constant, use the following code:

python
# `var_type` is the type of your global constant
# `var_name` is the name of your global constant (generate a unique name each time)
var = ir.GlobalVariable(self.module, var_type, var_name)
var.global_constant = True
var.linkage = 'internal'
var.initializer = # the contents of your constant, set via `ir.Constant()`


## Subtask 9 (15 points)

The last step is to support structs. They look like classes in Python and struct in C and contain many values of different types inside them.

You must support the following:
- Support struct declarations. Like function declarations, they may come in any order. So, you should process them in two passes: first declare all the structs, then set their fields. _Hint_: to create a named struct in LLVM, use self.module.context.get_identified_type() function. Figure out the rest yourself.
- Support . and -> operators (both return an lvalue). These ones must behave just like in C, and x->y is a shorthand for (*x).y.
- Support using struct types and pointers to struct types inside programs.

This is the last subtask, and if you complete it, you wrote the compiler for EPIC++ entirely. Hooray!

here is the codegen.py 
import sys
from antlr4 import FileStream, CommonTokenStream
from antlr4.error.ErrorListener import ErrorListener, ConsoleErrorListener
from EpicPPLexer import EpicPPLexer
from EpicPPParser import EpicPPParser
from EpicPPVisitor import EpicPPVisitor
from llvmlite import ir


class ErrorHandler(ErrorListener):
    def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
        _ = recognizer
        _ = offendingSymbol
        _ = e
        print(f'syntax error: {line}:{column}: {msg}')
        sys.exit(1)


class IRBuilder(EpicPPVisitor):
    def __inject_system_funcs(self):
        '''inject pre-defined system functions that you will need for your implementation'''

        self.__calloc_func = ir.Function(self.module, ir.FunctionType(
            args=[ir.IntType(64), ir.IntType(64)],
            return_type=ir.PointerType(ir.IntType(8)),
        ), name='$sys_calloc')

        self.__free_func = ir.Function(self.module, ir.FunctionType(
            args=[ir.PointerType(ir.IntType(8))],
            return_type=ir.VoidType(),
        ), name='$sys_free')

        self.__write_i64_func = ir.Function(self.module, ir.FunctionType(
            args=[ir.IntType(64)],
            return_type=ir.VoidType(),
        ), name='$sys_write_i64')

        self.__write_f64_func = ir.Function(self.module, ir.FunctionType(
            args=[ir.DoubleType()],
            return_type=ir.VoidType(),
        ), name='$sys_write_f64')

        self.__write_bool_func = ir.Function(self.module, ir.FunctionType(
            args=[ir.IntType(1)],
            return_type=ir.VoidType(),
        ), name='$sys_write_bool')

        self.__write_char_func = ir.Function(self.module, ir.FunctionType(
            args=[ir.IntType(8)],
            return_type=ir.VoidType(),
        ), name='$sys_write_char')

        self.__write_str_func = ir.Function(self.module, ir.FunctionType(
            args=[ir.PointerType(ir.IntType(8))],
            return_type=ir.VoidType(),
        ), name='$sys_write_str')

    def __init__(self):
        self.module = ir.Module(name='main')
        self.__inject_system_funcs()
        # TODO: write some code.

    # TODO: write some code.


def codegen(filename: str) -> ir.Module:
    # create input stream
    in_stream = FileStream(filename)

    # create a lexer
    lexer = EpicPPLexer(in_stream)
    lexer.addErrorListener(ErrorHandler())
    lexer.removeErrorListener(ConsoleErrorListener.INSTANCE)

    # create a parser with the lexer as an input
    stream = CommonTokenStream(lexer)
    parser = EpicPPParser(stream)
    parser.addErrorListener(ErrorHandler())
    parser.removeErrorListener(ConsoleErrorListener.INSTANCE)

    # use the parser to obtain an abstract syntax tree
    tree = parser.program()

    # create a builder
    builder = IRBuilder()

    # traverse the tree and build the IR
    # TODO: write some code.

    return builder.module
modify it to solve all subtasks