diff --git a/src/bonus/functional.asm b/src/bonus/functional.asm
index 6e25a304ffbb6f6043a5257b5a0ed08e07adc4d4..06aa28b0c59c27e134a197456a5f61ee34e3ccb8 100644
--- a/src/bonus/functional.asm
+++ b/src/bonus/functional.asm
@@ -1,4 +1,5 @@
-[BITS 64]
+; Interpret as 64 bits code
+[bits 64]
 
 ; nu uitati sa scrieti in feedback ca voiati
 ; assembly pe 64 de biti
@@ -7,23 +8,25 @@ section .text
 global map
 global reduce
 map:
-	push rbp        ; look at these fancy registers
-	mov rbp, rsp
+    ; look at these fancy registers
+    push rbp
+    mov rbp, rsp
 
-	; sa-nceapa turneu'
+    ; sa-nceapa turneu'
 
-	leave
-	ret
+    leave
+    ret
 
 
 ; int reduce(int *dst, int *src, int n, int acc_init, int(*f)(int, int));
 ; int f(int acc, int curr_elem);
 reduce:
-	push rbp        ; look at these fancy registers
-	mov rbp, rsp
+    ; look at these fancy registers
+    push rbp
+    mov rbp, rsp
 
-	; sa-nceapa festivalu'
+    ; sa-nceapa festivalu'
 
-	leave
-	ret
+    leave
+    ret
 
diff --git a/src/linter/cmd_file.py b/src/linter/cmd_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..862f04ca3713e2a1c623f9fd051e3904d4194bb5
--- /dev/null
+++ b/src/linter/cmd_file.py
@@ -0,0 +1,45 @@
+
+from linter_rules_file import Linter
+
+def main():
+    import argparse
+
+    parser = argparse.ArgumentParser('Linter homework-3')
+    parser.add_argument(
+        'file',
+        help='File to lint',
+    )
+
+    args = parser.parse_args()
+
+    linter = Linter(args.file)
+    linter.lint()
+
+    for f in linter.findings:
+        print(f)
+
+
+def multi(argv=None) -> int:
+    import argparse
+
+    parser = argparse.ArgumentParser('Linter homework-3')
+    parser.add_argument(
+        'files',
+        nargs='*',
+        help='Filenames to lint',
+    )
+    args = parser.parse_args(argv)
+
+    return_code = 0
+    for filename in args.files:
+        linter = Linter(filename)
+        linter.lint()
+
+        if linter.findings:
+            print(f'--- {filename}')
+            for f in linter.findings:
+                print(f)
+
+            return_code = 1
+
+    return return_code
diff --git a/src/linter/finding_file.py b/src/linter/finding_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..b38ea5582fe1a70b754561a220a33b0518ac619b
--- /dev/null
+++ b/src/linter/finding_file.py
@@ -0,0 +1,39 @@
+
+class Finding:
+    def __init__(
+        self,
+        message: str,
+        line_number: int = 0,
+        columns: tuple = (),
+        source: str = '',
+    ) -> None:
+        self.message = message
+        self.line_number = line_number
+        self.columns = columns
+        self.source = source
+
+        if not self.source:
+            if self.line_number > 0:
+                raise ValueError('Line number set but no source given.')
+            elif len(self.columns) > 0:
+                raise ValueError('Columns set but no source given.')
+
+    def __str__(self) -> str:
+        lines = [f'E:: {self.message}']
+        if self.line_number:
+            line_prefix = f'{self.line_number}: '
+            line_prefix_len = len(line_prefix)
+            lines.append(line_prefix + self.source.rstrip())
+            carrot_line = ' ' * line_prefix_len
+            if len(self.columns) == 2:
+                carrot_line += ' ' * self.columns[0]
+                carrot_line += '^' * (self.columns[1] - self.columns[0])
+                lines.append(carrot_line)
+            elif len(self.columns) == 1:
+                carrot_line += ' ' * self.columns[0]
+                carrot_line += '^'
+                lines.append(carrot_line)
+
+        return '\n'.join(lines)
+
+    __repr__ = __str__
diff --git a/src/linter/linter-script-file b/src/linter/linter-script-file
new file mode 100755
index 0000000000000000000000000000000000000000..fb572693d38ab57b3e5df5e11e199edbe255848d
--- /dev/null
+++ b/src/linter/linter-script-file
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+
+import re
+import sys
+
+import cmd_file
+
+if __name__ == '__main__':
+    sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
+    sys.exit(cmd_file.main())
diff --git a/src/linter/linter_rules_file.py b/src/linter/linter_rules_file.py
new file mode 100644
index 0000000000000000000000000000000000000000..d9d0929a40e47ff831ff4ab7ab71f341baf8c7e2
--- /dev/null
+++ b/src/linter/linter_rules_file.py
@@ -0,0 +1,404 @@
+import os
+import re
+import string
+
+from finding_file import Finding
+
+
+class Linter:
+    SENTIAL_EMPTY_LINES = []
+
+    def __init__(self, file: str) -> None:
+        self._file_path = file
+        self._file: str = os.path.basename(self._file_path)
+        self._findings: list[Finding] = []
+        self.__lines: list[str] = self.SENTIAL_EMPTY_LINES
+
+    @property
+    def _lines(self):
+        if self.__lines is self.SENTIAL_EMPTY_LINES:
+            with open(self._file_path) as fp:
+                self.__lines = fp.readlines()
+        return self.__lines
+
+    def lint(self):
+        # self._check_preamble()
+        # self._check_file_name()
+        # self._check_file_name_main()
+        # self._check_data_section_follows_text_section()
+        self._check_instructions_lowercase()
+        self._check_registers_lowercase()
+        self._check_line_empty_with_nonzero_space()
+        self._check_spaces()
+        self._check_magic_numbers()
+        self._check_section_placement()
+        self._check_instructions_indentation()
+        self._check_instructions_and_comments_on_the_same_line()
+
+    def _check_instructions_and_comments_on_the_same_line(self):
+        for i, line in enumerate(self._lines, start=1):
+            tmp_line = line.strip()
+
+            if self._check_is_instruction_line(tmp_line) and ";" in line:
+                self._findings.append(Finding('Comments must be placed on the previous line',
+                                                line_number=i,
+                                                source=line))
+
+    def _check_instructions_indentation(self):
+        """
+        Ensure instructions have the same indentation
+        """
+
+        target_indentation = -1
+        inside_text_section = False
+
+        for i, line in enumerate(self._lines, start=1):
+            tmp_line = line.strip()
+
+            if self._check_is_instruction_line(line):
+
+                if tmp_line.startswith('section .text'):
+                    inside_text_section = True
+                    continue
+
+                elif tmp_line.startswith('section'):
+                    inside_text_section = False
+
+                if inside_text_section is True and not tmp_line.startswith('global'):
+                    if target_indentation == -1:
+                        target_indentation = len(line) - len(line.lstrip())
+
+                    elif target_indentation != len(line) - len(line.lstrip()):
+                        self._findings.append(Finding('Instructions must have the same level of indentation',
+                                                line_number=i,
+                                                source=line))
+
+    def _check_section_placement(self):
+        """
+        Ensure section statements are at the beginning of the line
+        """
+
+        for i, line in enumerate(self._lines, start=1):
+            tmp_line = line.replace('\r', '').replace('\n', '').replace(' ', '')
+
+            if line.startswith(' ') and tmp_line.startswith("section") and self._check_is_instruction_line(line):
+                self._findings.append(Finding('Sections must be alligned to the start of the line',
+                                              line_number=i,
+                                              source=line))
+                
+    def _check_magic_numbers(self):
+        """
+        Ensure magic numbers have are explained.
+        """
+
+        prev = ""
+        for i, line in enumerate(self._lines, start=1):
+            tmp_line = line.replace('\r', '').replace('\n', '')
+
+            my_list = re.findall(r'(?<![a-zA-Z\'_])\b\d+\b(?![a-zA-Z\'_])', tmp_line)
+            
+            if len(my_list) > 0 and self._check_is_instruction_line(line) and not self._check_is_comment_line(prev) and ";" not in line:
+                self._findings.append(Finding('You must explain the meaning of magic numbers on the previous line',
+                                      line_number=i,
+                                      source=line))
+            
+            prev = line
+
+    def _check_preamble(self):
+        """
+        Ensure the preamble is present and well formatted.
+        """
+        preamble = []
+        for i, line in enumerate(self._lines, start=1):
+            if not self._check_is_comment_line(line):
+                break
+
+            preamble.append((i, line.strip().lstrip('#').lstrip()))
+
+        line_by_key: dict[str, tuple] = {}
+        for i, line in preamble:
+            key = line.split()
+            if not key:
+                continue
+
+            key = key[0].rstrip(':').strip().lower()
+            line_by_key[key] = (i, line)
+
+        program_line = line_by_key.get('program') or ()
+        if not program_line:
+            self._findings.append(Finding(
+                'Preamble error: No "Program Name" line found.',
+            ))
+        else:
+            self._check_preamble_program_line(*program_line)
+
+        author_line = line_by_key.get('author')
+        if not author_line:
+            self._findings.append(Finding(
+                'Preamble error: No "Author" line found.',
+            ))
+        else:
+            self._check_preamble_author_line(*author_line)
+
+        date_line = line_by_key.get('date')
+        if not date_line:
+            self._findings.append(Finding(
+                'Preamble error: No "Date" line found.',
+            ))
+        else:
+            self._check_preamble_date_line(*date_line)
+
+        purpose_line = line_by_key.get('purpose')
+        if not purpose_line:
+            self._findings.append(Finding(
+                'Preamble error: No "Purpose" line found.',
+            ))
+        else:
+            self._check_preamble_purpose_line(*purpose_line)
+
+        functions_line = line_by_key.get('functions')
+        if not functions_line:
+            self._findings.append(Finding(
+                'Preamble error: No "Functions" line found.',
+            ))
+        else:
+            self._check_preamble_functions_line(*functions_line)
+
+    def _check_file_name(self):
+        """
+        Check the file name follows the correct conventions.
+        """
+        name = self._file[:-2]
+
+        invalidChars = set(name) - set(string.ascii_letters)
+        if invalidChars:
+            self._findings.append(Finding(
+                f'File name contains invalid characters: {invalidChars}',
+            ))
+
+        if name[0] not in string.ascii_lowercase:
+            self._findings.append(Finding(
+                'File starts with non-lowercase letter.',
+            ))
+
+    def _check_file_name_main(self):
+        """
+        Check that if file has "main" function, it has "Main" in its name.
+        """
+        for i, line in enumerate(self._lines, start=1):
+            if self._check_is_comment_line(line):
+                continue
+
+            if self._check_is_instruction_line(line):
+                continue
+
+            if line.strip().startswith('main:'):
+                if not self._file.endswith('Main.s'):
+                    self._findings.append(Finding(
+                        'File name does not end with "Main" when it should.',
+                        line_number=i,
+                        source=line,
+                    ))
+                break
+        else:
+            if self._file.endswith('Main.s'):
+                self._findings.append(Finding(
+                    'File name ends with "Main" but no main function found.',
+                ))
+
+    def _check_data_section_follows_text_section(self):
+        """
+        Check that all data sections follow text sections.
+        """
+        # TODO: Make this loop reset on new functions.
+        can_see_data = False
+        for i, line in enumerate(self._lines, start=1):
+            if line.strip().startswith('section .text'):
+                can_see_data = True
+                continue
+
+            if line.strip().startswith('section .data'):
+                if can_see_data:
+                    can_see_data = False
+                    continue
+                else:
+                    self._findings.append(Finding(
+                        'Data sections must follow a text section.',
+                        line_number=i,
+                        source=line,
+                    ))
+
+    def _check_instructions_lowercase(self):
+        """
+        Check that instructions are lowercase.
+        """
+        for i, line in enumerate(self._lines, start=1):
+            if not self._check_is_instruction_line(line):
+                continue
+
+            if not line.strip().split()[0].islower():
+                self._findings.append(Finding(
+                    'Instruction is not lowercase.',
+                    line_number=i,
+                    columns=(len(line) - len(line.lstrip()),),
+                    source=line,
+                ))
+
+    def _check_registers_lowercase(self):
+        """
+        Check registers are listed in lowercase.
+        """
+        for i, line in enumerate(self._lines, start=1):
+            if not self._check_is_instruction_line(line):
+                continue
+
+            chunk = line
+            m = True  # To get things started.
+            pos = 0
+            while m:
+                m = re.search(r'[ ,]R\d{1,16}([ ,])?', chunk)
+                if m:
+                    self._findings.append(Finding(
+                        'Register is not lowercase.',
+                        line_number=i,
+                        columns=(pos + m.start(),),
+                        source=line,
+                    ))
+                    pos += m.end() - 1
+                    chunk = line[pos:]
+
+    def _check_line_empty_with_nonzero_space(self):
+        """
+        Check check that empty lines have no trailing whitespace.
+        """
+        for i, line in enumerate(self._lines, start=1):
+            tmp_line = line.replace('\r', '').replace('\n', '')
+            if len(tmp_line) > 0 and len(line.strip()) == 0:
+                self._findings.append(Finding(
+                    'Non-functional whitespace found.',
+                    line_number=i,
+                    columns=(0, len(line)),
+                    source=line,
+                ))
+
+    def _check_spaces(self):
+        """
+        Check each non-comment line to check that it does not have tabs.
+        """
+        for i, line in enumerate(self._lines, start=1):
+            try:
+                self._findings.append(Finding(
+                    'Tab found. Only spaces allowed.',
+                    line_number=i,
+                    columns=(line.index('\t'),),
+                    source=line,
+                ))
+            except ValueError:
+                pass
+
+    def _check_is_comment_line(self, line: str):
+        """
+        Check if it is a comment line
+        """
+        
+        return bool(re.match(r'^\s*;', line))
+
+    def _check_is_function_line(self, line: str):
+        return bool(re.match(r'^[_a-zA-Z0-9]+:\s*$', line))
+
+    def _check_is_instruction_line(self, line: str) -> bool:
+        """
+        Return true if line holds operation instructions.
+        """
+        # Ignore comment lines.
+        if self._check_is_comment_line(line):
+            return False
+
+        # Ignore empty lines.
+        if not line.strip():
+            return False
+
+        # Ignore empty lines that start sections.
+        if line.strip().startswith('.'):
+            return False
+
+        # Ignore function title lines.
+        if ':' in line:
+            return False
+
+        return True
+
+    def _check_preamble_program_line(self, line_number, line):
+        parts = list(map(str.strip, line.split(':')))
+        if len(parts) != 2:
+            self._findings.append(Finding(
+                'Invalid "Program Name" line found.',
+                line_number=line_number,
+                source=line,
+            ))
+
+        if parts[0] != 'Program Name':
+            self._findings.append(Finding(
+                'Invalid "Program Name" line found.',
+                line_number=line_number,
+                source=line,
+                columns=(0, len(parts[0])),
+            ))
+
+        if parts[1] != self._file:
+            self._findings.append(Finding(
+                'File in "Program Name" is not equivalent to file name.',
+                line_number=line_number,
+                source=line,
+                columns=(line.index(':') + 2, len(line)),
+            ))
+
+    def _check_preamble_author_line(self, line_number, line):
+        # TODO: Implement a more sophisticated check.
+        pass
+
+    def _check_preamble_date_line(self, line_number, line):
+        # TODO: Implement a more sophisticated check.
+        pass
+
+    def _check_preamble_purpose_line(self, line_number, line):
+        # TODO: Implement a more sophisticated check.
+        pass
+
+    def _check_preamble_functions_line(self, line_number, line):
+        # Get functions from function line.
+        line_functions = set(line.split()[1:])
+
+        # Get functions from file.
+        file_functions = set()
+        for tmp_line in self._lines:
+            if self._check_is_function_line(tmp_line):
+                file_functions.add(tmp_line.strip().rstrip(':'))
+
+        # function in line but not in file
+        missing_functions = line_functions - file_functions
+        if missing_functions:
+            for missing_function in missing_functions:
+                i = line.index(missing_function)
+                self._findings.append(Finding(
+                    f'Function {missing_function} listed in '
+                    'Functions line but not in file.',
+                    line_number=line_number,
+                    source=line,
+                    columns=(i, i + len(missing_function)),
+                ))
+
+        # function in line but not in file
+        missing_functions = file_functions - line_functions
+        if missing_functions:
+            for missing_function in missing_functions:
+                self._findings.append(Finding(
+                    f'Function {missing_function} in file '
+                    'but not listed in Functions line.',
+                    line_number=line_number,
+                    source=line,
+                ))
+
+    @property
+    def findings(self):
+        return self._findings
diff --git a/src/task-1/paranthesinator.asm b/src/task-1/paranthesinator.asm
index a9235cd41a1666572ff689bfe0eb555c0850bb7f..8501670673e2352b127fb29dd1eebff5ecad8a1c 100644
--- a/src/task-1/paranthesinator.asm
+++ b/src/task-1/paranthesinator.asm
@@ -1,4 +1,5 @@
-[BITS 32]
+; Interpret as 32 bits code
+[bits 32]
 
 %include "../include/io.mac"
 
@@ -6,10 +7,10 @@ section .text
 ; int check_parantheses(char *str)
 global check_parantheses
 check_parantheses:
-	push ebp
-	mov ebp, esp
+    push ebp
+    mov ebp, esp
 
-	; sa-nceapa concursul
+    ; sa-nceapa concursul
 
-	leave
-	ret
+    leave
+    ret
diff --git a/src/task-2/subtask_1.asm b/src/task-2/subtask_1.asm
index 010cbdc797744eaa1bc6fd2a21c535f3a7e95bc3..4ba938bbfea4d5878291750e5c35ddc8f2efe85e 100644
--- a/src/task-2/subtask_1.asm
+++ b/src/task-2/subtask_1.asm
@@ -5,6 +5,7 @@ section .text
     ;; no extern functions allowed
 
 quick_sort:
+    ;; create the new stack frame
     enter 0, 0
 
     ;; save the preserved registers
diff --git a/src/task-2/subtask_2.asm b/src/task-2/subtask_2.asm
index fda3a47f11ecf2b98c9fafc6c406d714a5053057..e49a2f5eb1c4269c485c18bddacb22db0795bee7 100644
--- a/src/task-2/subtask_2.asm
+++ b/src/task-2/subtask_2.asm
@@ -5,6 +5,7 @@ section .text
     ;; no extern functions allowed
 
 binary_search:
+    ;; create the new stack frame
     enter 0, 0
 
     ;; save the preserved registers
diff --git a/src/task-3/task_3.asm b/src/task-3/task_3.asm
index 056dba86193055664bb5eec894f23816f0a36198..33d3cf0aec7b74231067046e648b2da0b368445b 100644
--- a/src/task-3/task_3.asm
+++ b/src/task-3/task_3.asm
@@ -9,11 +9,13 @@ struc neighbours_t
 endstruc
 
 section .bss
-visited resd 10000   ; Vector for keeping track of visited nodes.
+; Vector for keeping track of visited nodes.
+visited resd 10000
 global visited
 
 section .data
-fmt_str db "%u", 10, 0     ; Format string for printf.
+; Format string for printf.
+fmt_str db "%u", 10, 0
 
 section .text
 global dfs