A compiler for the IFJ25 language, implemented in C. Developed as a team project for the IFJ (Formal Languages and Compilers) course at the Faculty of Information Technology, Brno University of Technology. The compiler translates IFJ25 source code into IFJcode25, a custom target/intermediate code.
Symbol table variant: BST, implemented as an AVL tree.
Implemented extensions: FUNEXP, CYCLES
This was developed by a team of four students. Task distribution:
| Member | Login | Main areas |
|---|---|---|
| Martin Mezei (lead) | xmezeim00 | Scanner FSM design, symbol table (AVL), semantic analysis, LL grammar design, code generation |
| Martin Raček (me) | xracekm00 | Lexical analysis (scanner.c), FSM design, Makefile, code generation, abstract syntax tree design |
| Samuel Fačka | xfackas00 | Syntax analysis (parser.c), LL grammar, AST implementation, recursive descent |
| Kristián Cilling | xcillik00 | Precedence analysis (operator parser), built-in function codegen, documentation |
All members contributed across design, implementation, testing and debugging beyond their primary areas.
The compiler pipeline consists of the following phases:
scanner.c/.h, lex_funs.c/.h
Converts source code into tokens using a set of finite state machines. Supports lookahead (push_token() / get_token()), string literals (single-line and multi-line), numeric literals, identifiers, keywords and comments. Identifiers are checked against a keyword table to distinguish keywords from user identifiers.
parser.c/.h, parser_expression.c/.h
Two-part syntax analysis:
- LL grammar with recursive descent, for statements and program structure
- Precedence (bottom-up) analysis for expressions: arithmetic, relational and type (
is) operators, range operators (..,...), and function calls
The parser builds an Abstract Syntax Tree (AST) during parsing. The precedence analyzer uses its own stack, implemented as a singly linked list, and a precedence table to decide shift/reduce operations.
ast.c/.h
A single generic node type (ASTNode) is used for all constructs, holding a node type, an array of child pointers, and a union of type-specific data. Expression subtrees are built as unbalanced binary trees but stored using the same child-array structure for consistency.
A pre-order traversal of the AST. Performs:
- Function and variable existence checks
- Redefinition checks
- Expression correctness and basic static type checking
- Argument validation for built-in function calls
symtable.c/.h, scope_stack.c/.h
The symbol table is implemented as an AVL tree, using a composite key (symbol name, argument count, identifier type). User-defined functions are stored in a dedicated global table (g_func_symtable), populated during syntax analysis. Global variables have their own table, populated during semantic analysis. Local variables use a separate symbol table per code block; these tables are pushed onto a scope stack, and lookups scan from the top of the stack down.
code_gen.c/.h, built_in_funs.c/.h
Code is generated during a second AST traversal. Unique variable and label names are produced by a global name generator, since each function has its own local frame. Function calls follow the Pascal calling convention. Built-in functions are implemented separately and emitted into every generated program.
- Code generation does not correctly handle nested branching or nested loops
- Semantic analysis does not detect a standalone global variable reference on its own line