automated commit TP3
This commit is contained in:
parent
530ff7e694
commit
2e15c1352e
9
MiniC/.coveragerc
Normal file
9
MiniC/.coveragerc
Normal file
@ -0,0 +1,9 @@
|
||||
[report]
|
||||
exclude_lines =
|
||||
pragma: no cover
|
||||
raise MiniCInternalError
|
||||
raise MiniCUnsupportedError
|
||||
if debug:
|
||||
if debug_graphs:
|
||||
if self._debug:
|
||||
if self._debug_graphs:
|
||||
16
MiniC/.gitignore
vendored
Normal file
16
MiniC/.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/MiniCLexer.py
|
||||
/MiniCParser.py
|
||||
/MiniCVisitor.py
|
||||
/TP*/tests/**/*.s
|
||||
/TP*/**/*.riscv
|
||||
/TP*/**/*-naive.s
|
||||
/TP*/**/*-all_in_mem.s
|
||||
/TP*/**/*-smart.s
|
||||
/TP*/**/*.trace
|
||||
/TP*/**/*.dot
|
||||
/TP*/**/*.dot.pdf
|
||||
/MiniC-stripped.g4
|
||||
.coverage
|
||||
htmlcov/
|
||||
doc/api
|
||||
doc/_build
|
||||
18
MiniC/Lib/Errors.py
Normal file
18
MiniC/Lib/Errors.py
Normal file
@ -0,0 +1,18 @@
|
||||
class MiniCRuntimeError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MiniCInternalError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MiniCUnsupportedError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MiniCTypeError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AllocationError(Exception):
|
||||
pass
|
||||
99
MiniC/Makefile
Normal file
99
MiniC/Makefile
Normal file
@ -0,0 +1,99 @@
|
||||
MYNAME = JohnDoe
|
||||
PACKAGE = MiniC
|
||||
# Example: stop at the first failed test:
|
||||
# make PYTEST_OPTS=-x test
|
||||
PYTEST_OPTS =
|
||||
# Run the whole test infrastructure for a subset of test files e.g.
|
||||
# make TEST_FILES='TP03/**/bad*.c' test
|
||||
ifdef TEST_FILES
|
||||
export TEST_FILES
|
||||
endif
|
||||
|
||||
ifdef SSA
|
||||
MINICC_OPTS+=--ssa
|
||||
endif
|
||||
|
||||
ifdef SSA_OPTIM
|
||||
MINICC_OPTS+=--ssa-optim
|
||||
endif
|
||||
|
||||
ifdef TYPECHECK_ONLY
|
||||
MINICC_OPTS+=--disable-codegen
|
||||
endif
|
||||
|
||||
ifdef PARSE_ONLY
|
||||
MINICC_OPTS+=--disable-typecheck --disable-codegen
|
||||
endif
|
||||
|
||||
export MINICC_OPTS
|
||||
|
||||
PYTEST_BASE_OPTS=-vv -rs --failed-first --cov="$(PWD)" --cov-report=term --cov-report=html
|
||||
|
||||
ifndef ANTLR4
|
||||
abort:
|
||||
$(error variable ANTLR4 is not set)
|
||||
endif
|
||||
|
||||
all: antlr
|
||||
|
||||
.PHONY: antlr
|
||||
antlr MiniCLexer.py MiniCParser.py: $(PACKAGE).g4
|
||||
$(ANTLR4) $< -Dlanguage=Python3 -visitor -no-listener
|
||||
|
||||
main-deps: MiniCLexer.py MiniCParser.py TP03/MiniCInterpretVisitor.py TP03/MiniCTypingVisitor.py
|
||||
|
||||
.PHONY: test test-interpret test-codegen clean clean-tests tar antlr
|
||||
|
||||
|
||||
test: test-interpret
|
||||
|
||||
|
||||
test-pyright: antlr
|
||||
pyright .
|
||||
|
||||
test-interpret: test-pyright test_interpreter.py main-deps
|
||||
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) test_interpreter.py
|
||||
|
||||
|
||||
# Test for naive allocator (also runs test_expect to check // EXPECTED directives):
|
||||
test-naive: test-pyright antlr
|
||||
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'naive or expect'
|
||||
|
||||
# Test for all but the smart allocator, i.e. everything that lab4 should pass:
|
||||
test-notsmart: test-pyright antlr
|
||||
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'not smart'
|
||||
|
||||
# Test just the smart allocator (quicker than tests)
|
||||
test-smart: test-pyright antlr
|
||||
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'smart'
|
||||
|
||||
# Complete testsuite (should pass for lab5):
|
||||
test-codegen: test-pyright antlr
|
||||
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py
|
||||
|
||||
tar: clean
|
||||
dir=$$(basename "$$PWD") && cd .. && \
|
||||
tar cvfz $(MYNAME).tgz --exclude="*.riscv" --exclude=".git" --exclude=".pytest_cache" \
|
||||
--exclude="htmlcov" "$$dir"
|
||||
@echo "Created ../$(MYNAME).tgz"
|
||||
|
||||
# Remove any assembly file that was created by a test.
|
||||
# Don't just find -name \*.s -exec rm {} \; because there may be legitimate .s files in the testsuite.
|
||||
define CLEAN
|
||||
import glob
|
||||
import os
|
||||
for f in glob.glob("**/tests/**/*.c", recursive=True):
|
||||
for s in ("{}-{}.s".format(f[:-2], test) for test in ("naive", "smart", "gcc", "all_in_mem")):
|
||||
try:
|
||||
os.remove(s)
|
||||
print("Removed {}".format(s))
|
||||
except OSError:
|
||||
pass
|
||||
endef
|
||||
export CLEAN
|
||||
clean-tests:
|
||||
@python3 -c "$$CLEAN"
|
||||
|
||||
clean: clean-tests
|
||||
find . \( -iname "*~" -or -iname ".cache*" -or -iname "*.diff" -or -iname "log*.txt" -or -iname "__pycache__" -or -iname "*.tokens" -or -iname "*.interp" \) -print0 | xargs -0 rm -rf \;
|
||||
rm -rf *~ $(PACKAGE)Parser.py $(PACKAGE)Lexer.py $(PACKAGE)Visitor.py .coverage .benchmarks
|
||||
141
MiniC/MiniC.g4
Normal file
141
MiniC/MiniC.g4
Normal file
@ -0,0 +1,141 @@
|
||||
grammar MiniC;
|
||||
|
||||
prog: function* EOF #progRule;
|
||||
|
||||
// For now, we don't have "real" functions, just the main() function
|
||||
// that is the main program, with a hardcoded profile and final
|
||||
// 'return 0'.
|
||||
function: INTTYPE ID OPAR CPAR OBRACE vardecl_l block
|
||||
RETURN INT SCOL CBRACE #funcDef;
|
||||
|
||||
vardecl_l: vardecl* #varDeclList;
|
||||
|
||||
vardecl: typee id_l SCOL #varDecl;
|
||||
|
||||
|
||||
id_l
|
||||
: ID #idListBase
|
||||
| ID COM id_l #idList
|
||||
;
|
||||
|
||||
block: stat* #statList;
|
||||
|
||||
stat
|
||||
: assignment SCOL
|
||||
| if_stat
|
||||
| while_stat
|
||||
| print_stat
|
||||
;
|
||||
|
||||
assignment: ID ASSIGN expr #assignStat;
|
||||
|
||||
if_stat: IF OPAR expr CPAR then_block=stat_block
|
||||
(ELSE else_block=stat_block)? #ifStat;
|
||||
|
||||
stat_block
|
||||
: OBRACE block CBRACE
|
||||
| stat
|
||||
;
|
||||
|
||||
while_stat: WHILE OPAR expr CPAR body=stat_block #whileStat;
|
||||
|
||||
|
||||
print_stat
|
||||
: PRINTLN_INT OPAR expr CPAR SCOL #printlnintStat
|
||||
| PRINTLN_FLOAT OPAR expr CPAR SCOL #printlnfloatStat
|
||||
| PRINTLN_BOOL OPAR expr CPAR SCOL #printlnboolStat
|
||||
| PRINTLN_STRING OPAR expr CPAR SCOL #printlnstringStat
|
||||
;
|
||||
|
||||
expr
|
||||
: MINUS expr #unaryMinusExpr
|
||||
| NOT expr #notExpr
|
||||
| expr myop=(MULT|DIV|MOD) expr #multiplicativeExpr
|
||||
| expr myop=(PLUS|MINUS) expr #additiveExpr
|
||||
| expr myop=(GT|LT|GTEQ|LTEQ) expr #relationalExpr
|
||||
| expr myop=(EQ|NEQ) expr #equalityExpr
|
||||
| expr AND expr #andExpr
|
||||
| expr OR expr #orExpr
|
||||
| atom #atomExpr
|
||||
;
|
||||
|
||||
atom
|
||||
: OPAR expr CPAR #parExpr
|
||||
| INT #intAtom
|
||||
| FLOAT #floatAtom
|
||||
| (TRUE | FALSE) #booleanAtom
|
||||
| ID #idAtom
|
||||
| STRING #stringAtom
|
||||
;
|
||||
|
||||
typee
|
||||
: mytype=(INTTYPE|FLOATTYPE|BOOLTYPE|STRINGTYPE) #basicType
|
||||
;
|
||||
|
||||
OR : '||';
|
||||
AND : '&&';
|
||||
EQ : '==';
|
||||
NEQ : '!=';
|
||||
GT : '>';
|
||||
LT : '<';
|
||||
GTEQ : '>=';
|
||||
LTEQ : '<=';
|
||||
PLUS : '+';
|
||||
MINUS : '-';
|
||||
MULT : '*';
|
||||
DIV : '/';
|
||||
MOD : '%';
|
||||
NOT : '!';
|
||||
|
||||
COL: ':';
|
||||
SCOL : ';';
|
||||
COM : ',';
|
||||
ASSIGN : '=';
|
||||
OPAR : '(';
|
||||
CPAR : ')';
|
||||
OBRACE : '{';
|
||||
CBRACE : '}';
|
||||
|
||||
TRUE : 'true';
|
||||
FALSE : 'false';
|
||||
IF : 'if';
|
||||
ELSE : 'else';
|
||||
WHILE : 'while';
|
||||
RETURN : 'return';
|
||||
PRINTLN_INT : 'println_int';
|
||||
PRINTLN_BOOL : 'println_bool';
|
||||
PRINTLN_STRING : 'println_string';
|
||||
PRINTLN_FLOAT : 'println_float';
|
||||
|
||||
INTTYPE: 'int';
|
||||
FLOATTYPE: 'float';
|
||||
STRINGTYPE: 'string';
|
||||
BOOLTYPE : 'bool';
|
||||
|
||||
ID
|
||||
: [a-zA-Z_] [a-zA-Z_0-9]*
|
||||
;
|
||||
|
||||
INT
|
||||
: [0-9]+
|
||||
;
|
||||
|
||||
FLOAT
|
||||
: [0-9]+ '.' [0-9]*
|
||||
| '.' [0-9]+
|
||||
;
|
||||
|
||||
STRING
|
||||
: '"' (~["\r\n] | '""')* '"'
|
||||
;
|
||||
|
||||
|
||||
COMMENT
|
||||
// # is a comment in Mini-C, and used for #include in real C so that we ignore #include statements
|
||||
: ('#' | '//') ~[\r\n]* -> skip
|
||||
;
|
||||
|
||||
SPACE
|
||||
: [ \t\r\n] -> skip
|
||||
;
|
||||
|
||||
310
MiniC/MiniCC.py
Normal file
310
MiniC/MiniCC.py
Normal file
@ -0,0 +1,310 @@
|
||||
#! /usr/bin/env python3
|
||||
"""
|
||||
Code generation lab, main file. Code Generation with Smart IRs.
|
||||
Usage:
|
||||
python3 MiniCC.py <filename>
|
||||
python3 MiniCC.py --help
|
||||
"""
|
||||
import traceback
|
||||
from typing import cast
|
||||
from MiniCLexer import MiniCLexer
|
||||
from MiniCParser import MiniCParser
|
||||
from TP03.MiniCTypingVisitor import MiniCTypingVisitor, MiniCTypeError
|
||||
from TP03.MiniCInterpretVisitor import MiniCInterpretVisitor
|
||||
from Lib.Errors import (MiniCUnsupportedError, MiniCInternalError,
|
||||
MiniCRuntimeError, AllocationError)
|
||||
|
||||
from enum import Enum
|
||||
import argparse
|
||||
|
||||
from antlr4 import FileStream, CommonTokenStream
|
||||
from antlr4.error.ErrorListener import ErrorListener
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
class Mode(Enum):
|
||||
PARSE = 0
|
||||
EVAL = 1
|
||||
LINEAR = 2
|
||||
CFG = 3
|
||||
SSA = 4
|
||||
OPTIM = 5
|
||||
|
||||
def is_codegen(self) -> bool:
|
||||
return self.value >= Mode.LINEAR.value
|
||||
|
||||
|
||||
def valid_modes():
|
||||
modes = ['parse', 'typecheck', 'eval']
|
||||
|
||||
try:
|
||||
import TP04.MiniCCodeGen3AVisitor # type: ignore[import]
|
||||
modes.append('codegen-linear')
|
||||
except ImportError:
|
||||
return modes
|
||||
|
||||
try:
|
||||
import Lib.CFG # type: ignore[import]
|
||||
modes.append('codegen-cfg')
|
||||
except ImportError:
|
||||
return modes
|
||||
|
||||
try:
|
||||
import TP05.SSA # type: ignore[import]
|
||||
modes.append('codegen-ssa')
|
||||
except ImportError:
|
||||
return modes
|
||||
|
||||
try:
|
||||
import TP05c.OptimSSA # type: ignore[import]
|
||||
modes.append('codegen-optim')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
return modes
|
||||
|
||||
|
||||
class CountErrorListener(ErrorListener):
|
||||
"""Count number of errors.
|
||||
|
||||
Parser provides getNumberOfSyntaxErrors(), but the Lexer
|
||||
apparently doesn't provide an easy way to know if an error occurred
|
||||
after the fact. Do the counting ourserves with a listener.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(CountErrorListener, self).__init__()
|
||||
self.count = 0
|
||||
|
||||
def syntaxError(self, recognizer, offending_symbol, line, column, msg, e):
|
||||
self.count += 1
|
||||
|
||||
|
||||
def main(inputname, reg_alloc, mode,
|
||||
typecheck=True, stdout=False, output_name=None, debug=False,
|
||||
debug_graphs=False, ssa_graphs=False):
|
||||
(basename, rest) = os.path.splitext(inputname)
|
||||
if mode.is_codegen():
|
||||
if stdout:
|
||||
output_name = None
|
||||
print("Code will be generated on standard output")
|
||||
elif output_name is None:
|
||||
output_name = basename + ".s"
|
||||
print("Code will be generated in file " + output_name)
|
||||
|
||||
input_s = FileStream(inputname, encoding='utf-8')
|
||||
lexer = MiniCLexer(input_s)
|
||||
counter = CountErrorListener()
|
||||
lexer._listeners.append(counter)
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = MiniCParser(stream)
|
||||
parser._listeners.append(counter)
|
||||
tree = parser.prog()
|
||||
if counter.count > 0:
|
||||
exit(3) # Syntax or lexicography errors occurred, don't try to go further.
|
||||
if typecheck:
|
||||
typing_visitor = MiniCTypingVisitor()
|
||||
try:
|
||||
typing_visitor.visit(tree)
|
||||
except MiniCTypeError as e:
|
||||
print(e.args[0])
|
||||
exit(2)
|
||||
|
||||
if mode == Mode.EVAL:
|
||||
# interpret Visitor
|
||||
interpreter_visitor = MiniCInterpretVisitor()
|
||||
try:
|
||||
interpreter_visitor.visit(tree)
|
||||
except MiniCRuntimeError as e:
|
||||
print(e.args[0])
|
||||
exit(1)
|
||||
except MiniCInternalError as e:
|
||||
print(e.args[0], file=sys.stderr)
|
||||
exit(4)
|
||||
return
|
||||
|
||||
if not mode.is_codegen():
|
||||
if debug:
|
||||
print("Not running code generation because of --typecheck-only.")
|
||||
return
|
||||
|
||||
# Codegen 3@ CFG Visitor, first argument is debug mode
|
||||
from TP04.MiniCCodeGen3AVisitor import MiniCCodeGen3AVisitor # type: ignore[import]
|
||||
visitor3 = MiniCCodeGen3AVisitor(debug, parser)
|
||||
|
||||
# dump generated code on stdout or file.
|
||||
with open(output_name, 'w') if output_name else sys.stdout as output:
|
||||
visitor3.visit(tree)
|
||||
for function in visitor3.get_functions():
|
||||
fdata = function.fdata
|
||||
# Allocation part
|
||||
if mode == Mode.LINEAR:
|
||||
code = function
|
||||
else:
|
||||
from TP04.BuildCFG import build_cfg # type: ignore[import]
|
||||
from Lib.CFG import CFG # type: ignore[import]
|
||||
code = build_cfg(function)
|
||||
assert(isinstance(code, CFG))
|
||||
if debug_graphs:
|
||||
s = "{}.{}.dot".format(basename, code.fdata._name)
|
||||
print("CFG:", s)
|
||||
code.print_dot(s, view=True)
|
||||
if mode.value >= Mode.SSA.value:
|
||||
from TP05.SSA import enter_ssa # type: ignore[import]
|
||||
from Lib.CFG import CFG # type: ignore[import]
|
||||
|
||||
DF = enter_ssa(cast(CFG, code), basename, debug, ssa_graphs)
|
||||
if ssa_graphs:
|
||||
s = "{}.{}.ssa.dot".format(basename, code.fdata._name)
|
||||
print("SSA:", s)
|
||||
code.print_dot(s, DF, True)
|
||||
if mode == Mode.OPTIM:
|
||||
from TP05c.OptimSSA import OptimSSA # type: ignore[import]
|
||||
OptimSSA(cast(CFG, code), debug=debug)
|
||||
if ssa_graphs:
|
||||
s = "{}.{}.optimssa.dot".format(basename, code.fdata._name)
|
||||
print("SSA after optim:", s)
|
||||
code.print_dot(s, view=True)
|
||||
allocator = None
|
||||
if reg_alloc == "naive":
|
||||
from Lib.Allocator import NaiveAllocator # type: ignore[import]
|
||||
allocator = NaiveAllocator(fdata)
|
||||
comment = "naive allocation"
|
||||
elif reg_alloc == "all-in-mem":
|
||||
from TP04.AllInMemAllocator import AllInMemAllocator # type: ignore[import]
|
||||
allocator = AllInMemAllocator(fdata)
|
||||
comment = "all-in-memory allocation"
|
||||
elif reg_alloc == "smart":
|
||||
liveness = None
|
||||
if mode == Mode.SSA:
|
||||
from TP05.LivenessSSA import LivenessSSA # type: ignore[import]
|
||||
try:
|
||||
from Lib.CFG import CFG # type: ignore[import]
|
||||
liveness = LivenessSSA(cast(CFG, code), debug=debug)
|
||||
except NameError:
|
||||
form = "CFG in SSA form"
|
||||
raise ValueError("Invalid dataflow form: \
|
||||
liveness file not found for {}.".format(form))
|
||||
else:
|
||||
try:
|
||||
from TP05.LivenessDataFlow import LivenessDataFlow # type: ignore[import]
|
||||
liveness = LivenessDataFlow(code, debug=debug)
|
||||
except NameError:
|
||||
form = "CFG not in SSA form"
|
||||
raise ValueError("Invalid dataflow form: \
|
||||
liveness file not found for {}.".format(form))
|
||||
from TP05.SmartAllocator import SmartAllocator # type: ignore[import]
|
||||
allocator = SmartAllocator(fdata, basename, liveness,
|
||||
debug, debug_graphs)
|
||||
comment = "smart allocation with graph coloring"
|
||||
elif reg_alloc == "none":
|
||||
comment = "non executable 3-Address instructions"
|
||||
else:
|
||||
raise ValueError("Invalid allocation strategy:" + reg_alloc)
|
||||
if allocator:
|
||||
allocator.prepare()
|
||||
if mode == Mode.SSA:
|
||||
from Lib.CFG import CFG # type: ignore[import]
|
||||
from TP05.SSA import exit_ssa # type: ignore[import]
|
||||
exit_ssa(cast(CFG, code))
|
||||
comment += " with SSA"
|
||||
if allocator:
|
||||
allocator.rewriteCode(code)
|
||||
if mode == Mode.SSA and ssa_graphs:
|
||||
s = "{}.{}.exitssa.dot".format(basename, code.fdata._name)
|
||||
print("CFG after SSA:", s)
|
||||
code.print_dot(s, view=True)
|
||||
code.print_code(output, comment=comment)
|
||||
if debug:
|
||||
visitor3.printSymbolTable()
|
||||
|
||||
|
||||
# command line management
|
||||
if __name__ == '__main__':
|
||||
|
||||
modes = valid_modes()
|
||||
|
||||
parser = argparse.ArgumentParser(description='Generate code for .c file')
|
||||
|
||||
parser.add_argument('filename', type=str,
|
||||
help='Source file.')
|
||||
parser.add_argument('--mode', type=str,
|
||||
choices=valid_modes(),
|
||||
required=True,
|
||||
help='Operation to perform on the input program')
|
||||
parser.add_argument('--debug', action='store_true',
|
||||
default=False,
|
||||
help='Emit verbose debug output')
|
||||
parser.add_argument('--disable-typecheck', action='store_true',
|
||||
default=False,
|
||||
help="Don't run the typechecker before evaluation or code generation")
|
||||
|
||||
if "codegen-linear" in modes:
|
||||
parser.add_argument('--reg-alloc', type=str,
|
||||
choices=['none', 'naive', 'all-in-mem', 'smart'],
|
||||
help='Register allocation to perform during code generation')
|
||||
parser.add_argument('--stdout', action='store_true',
|
||||
help='Generate code to stdout')
|
||||
parser.add_argument('--output', type=str,
|
||||
help='Generate code to outfile')
|
||||
|
||||
if "codegen-cfg" in modes:
|
||||
parser.add_argument('--graphs', action='store_true',
|
||||
default=False,
|
||||
help='Display graphs (CFG, conflict graph).')
|
||||
|
||||
if "codegen-ssa" in modes:
|
||||
parser.add_argument('--ssa-graphs', action='store_true',
|
||||
default=False,
|
||||
help='Display SSA graphs (DT, DF).')
|
||||
|
||||
args = parser.parse_args()
|
||||
reg_alloc = args.reg_alloc if "codegen-linear" in modes else None
|
||||
to_stdout = args.stdout if "codegen-linear" in modes else False
|
||||
outfile = args.output if "codegen-linear" in modes else None
|
||||
graphs = args.graphs if "codegen-cfg" in modes else False
|
||||
ssa_graphs = args.ssa_graphs if "codegen-ssa" in modes else False
|
||||
|
||||
if reg_alloc is None and "codegen" in args.mode:
|
||||
print("error: the following arguments is required: --reg-alloc")
|
||||
exit(1)
|
||||
elif reg_alloc is not None and "codegen" not in args.mode:
|
||||
print("error: register allocation is only available in code generation mode")
|
||||
exit(1)
|
||||
|
||||
typecheck = not args.disable_typecheck
|
||||
|
||||
if args.mode == "parse":
|
||||
mode = Mode.PARSE
|
||||
typecheck = False
|
||||
elif args.mode == "typecheck":
|
||||
mode = Mode.PARSE
|
||||
elif args.mode == "eval":
|
||||
mode = Mode.EVAL
|
||||
elif args.mode == "codegen-linear":
|
||||
mode = Mode.LINEAR
|
||||
if reg_alloc == "smart":
|
||||
print("error: smart register allocation is not compatible with linear code generation")
|
||||
exit(1)
|
||||
elif args.mode == "codegen-cfg":
|
||||
mode = Mode.CFG
|
||||
elif args.mode == "codegen-ssa":
|
||||
mode = Mode.SSA
|
||||
elif args.mode == "codegen-optim":
|
||||
mode = Mode.OPTIM
|
||||
else:
|
||||
raise ValueError("Invalid mode:" + args.mode)
|
||||
|
||||
try:
|
||||
main(args.filename, reg_alloc, mode,
|
||||
typecheck,
|
||||
to_stdout, outfile, args.debug,
|
||||
graphs, ssa_graphs)
|
||||
except MiniCUnsupportedError as e:
|
||||
print(e)
|
||||
exit(5)
|
||||
except (MiniCInternalError, AllocationError):
|
||||
traceback.print_exc()
|
||||
exit(4)
|
||||
32
MiniC/README-interpreter.md
Normal file
32
MiniC/README-interpreter.md
Normal file
@ -0,0 +1,32 @@
|
||||
# MiniC interpreter and typer
|
||||
LAB3, MIF08 / CAP 2022-23
|
||||
|
||||
|
||||
# Authors
|
||||
|
||||
TODO: YOUR NAME HERE
|
||||
|
||||
# Contents
|
||||
|
||||
TODO for STUDENTS : Say a bit about the code infrastructure ...
|
||||
|
||||
# Howto
|
||||
|
||||
`make test-interpret TEST_FILES='TP03/tests/provided/examples/test_print_int.c'` for a single run
|
||||
|
||||
`make test` to test all the files in `*/tests/*` according to `EXPECTED` results.
|
||||
|
||||
You can select the files you want to test by using `make test TEST_FILES='TP03/**/*bad*.c'` (`**` means
|
||||
"any number of possibly nested directories").
|
||||
|
||||
# Test design
|
||||
|
||||
TODO: explain your tests. Do not repeat what test files already contain, just give the main objectives of the tests.
|
||||
|
||||
# Design choices
|
||||
|
||||
TODO: explain your choices - explain the limitations of your implementation.
|
||||
|
||||
# Known bugs
|
||||
|
||||
TODO: document any known bug and limitations. Did you do everything asked for? Did you implement an extension?
|
||||
178
MiniC/TP03/MiniCInterpretVisitor.py
Normal file
178
MiniC/TP03/MiniCInterpretVisitor.py
Normal file
@ -0,0 +1,178 @@
|
||||
# Visitor to *interpret* MiniC files
|
||||
from typing import Dict, List, cast
|
||||
from MiniCVisitor import MiniCVisitor
|
||||
from MiniCParser import MiniCParser
|
||||
from Lib.Errors import MiniCRuntimeError, MiniCInternalError
|
||||
|
||||
MINIC_VALUE = int | str | bool | float | List['MINIC_VALUE']
|
||||
|
||||
|
||||
class MiniCInterpretVisitor(MiniCVisitor):
|
||||
|
||||
_memory: Dict[str, MINIC_VALUE]
|
||||
|
||||
def __init__(self):
|
||||
self._memory = dict() # store all variable ids and values.
|
||||
self.has_main = False
|
||||
|
||||
# visitors for variable declarations
|
||||
|
||||
def visitVarDecl(self, ctx) -> None:
|
||||
# Initialise all variables in self._memory
|
||||
type_str = ctx.typee().getText()
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitIdList(self, ctx) -> List[str]:
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitIdListBase(self, ctx) -> List[str]:
|
||||
return [ctx.ID().getText()]
|
||||
|
||||
# visitors for atoms --> value
|
||||
|
||||
def visitParExpr(self, ctx) -> MINIC_VALUE:
|
||||
return self.visit(ctx.expr())
|
||||
|
||||
def visitIntAtom(self, ctx) -> int:
|
||||
return int(ctx.getText())
|
||||
|
||||
def visitFloatAtom(self, ctx) -> float:
|
||||
return float(ctx.getText())
|
||||
|
||||
def visitBooleanAtom(self, ctx) -> bool:
|
||||
return ctx.getText() == "true"
|
||||
|
||||
def visitIdAtom(self, ctx) -> MINIC_VALUE:
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitStringAtom(self, ctx) -> str:
|
||||
return ctx.getText()[1:-1] # Remove the ""
|
||||
|
||||
# visit expressions
|
||||
|
||||
def visitAtomExpr(self, ctx) -> MINIC_VALUE:
|
||||
return self.visit(ctx.atom())
|
||||
|
||||
def visitOrExpr(self, ctx) -> bool:
|
||||
lval = self.visit(ctx.expr(0))
|
||||
rval = self.visit(ctx.expr(1))
|
||||
return lval | rval
|
||||
|
||||
def visitAndExpr(self, ctx) -> bool:
|
||||
lval = self.visit(ctx.expr(0))
|
||||
rval = self.visit(ctx.expr(1))
|
||||
return lval & rval
|
||||
|
||||
def visitEqualityExpr(self, ctx) -> bool:
|
||||
assert ctx.myop is not None
|
||||
lval = self.visit(ctx.expr(0))
|
||||
rval = self.visit(ctx.expr(1))
|
||||
# be careful for float equality
|
||||
if ctx.myop.type == MiniCParser.EQ:
|
||||
return lval == rval
|
||||
else:
|
||||
return lval != rval
|
||||
|
||||
def visitRelationalExpr(self, ctx) -> bool:
|
||||
assert ctx.myop is not None
|
||||
lval = self.visit(ctx.expr(0))
|
||||
rval = self.visit(ctx.expr(1))
|
||||
if ctx.myop.type == MiniCParser.LT:
|
||||
return lval < rval
|
||||
elif ctx.myop.type == MiniCParser.LTEQ:
|
||||
return lval <= rval
|
||||
elif ctx.myop.type == MiniCParser.GT:
|
||||
return lval > rval
|
||||
elif ctx.myop.type == MiniCParser.GTEQ:
|
||||
return lval >= rval
|
||||
else:
|
||||
raise MiniCInternalError(
|
||||
"Unknown comparison operator '%s'" % ctx.myop
|
||||
)
|
||||
|
||||
def visitAdditiveExpr(self, ctx) -> MINIC_VALUE:
|
||||
assert ctx.myop is not None
|
||||
lval = self.visit(ctx.expr(0))
|
||||
rval = self.visit(ctx.expr(1))
|
||||
if ctx.myop.type == MiniCParser.PLUS:
|
||||
if any(isinstance(x, str) for x in (lval, rval)):
|
||||
return '{}{}'.format(lval, rval)
|
||||
else:
|
||||
return lval + rval
|
||||
elif ctx.myop.type == MiniCParser.MINUS:
|
||||
return lval - rval
|
||||
else:
|
||||
raise MiniCInternalError(
|
||||
"Unknown additive operator '%s'" % ctx.myop)
|
||||
|
||||
def visitMultiplicativeExpr(self, ctx) -> MINIC_VALUE:
|
||||
assert ctx.myop is not None
|
||||
lval = self.visit(ctx.expr(0))
|
||||
rval = self.visit(ctx.expr(1))
|
||||
if ctx.myop.type == MiniCParser.MULT:
|
||||
return lval * rval
|
||||
elif ctx.myop.type == MiniCParser.DIV:
|
||||
if rval == 0:
|
||||
raise MiniCRuntimeError("Division by 0")
|
||||
if isinstance(lval, int):
|
||||
return lval // rval
|
||||
else:
|
||||
return lval / rval
|
||||
elif ctx.myop.type == MiniCParser.MOD:
|
||||
# TODO : interpret modulo
|
||||
raise NotImplementedError()
|
||||
else:
|
||||
raise MiniCInternalError(
|
||||
"Unknown multiplicative operator '%s'" % ctx.myop)
|
||||
|
||||
def visitNotExpr(self, ctx) -> bool:
|
||||
return not self.visit(ctx.expr())
|
||||
|
||||
def visitUnaryMinusExpr(self, ctx) -> MINIC_VALUE:
|
||||
return -self.visit(ctx.expr())
|
||||
|
||||
# visit statements
|
||||
|
||||
def visitPrintlnintStat(self, ctx) -> None:
|
||||
val = self.visit(ctx.expr())
|
||||
print(val)
|
||||
|
||||
def visitPrintlnfloatStat(self, ctx) -> None:
|
||||
val = self.visit(ctx.expr())
|
||||
if isinstance(val, float):
|
||||
val = "%.2f" % val
|
||||
print(val)
|
||||
|
||||
def visitPrintlnboolStat(self, ctx) -> None:
|
||||
val = self.visit(ctx.expr())
|
||||
print('1' if val else '0')
|
||||
|
||||
def visitPrintlnstringStat(self, ctx) -> None:
|
||||
val = self.visit(ctx.expr())
|
||||
print(val)
|
||||
|
||||
def visitAssignStat(self, ctx) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitIfStat(self, ctx) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitWhileStat(self, ctx) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
# TOPLEVEL
|
||||
def visitProgRule(self, ctx) -> None:
|
||||
self.visitChildren(ctx)
|
||||
if not self.has_main:
|
||||
# A program without a main function is compilable (hence
|
||||
# it's not a typing error per se), but not executable,
|
||||
# hence we consider it a runtime error.
|
||||
raise MiniCRuntimeError("No main function in file")
|
||||
|
||||
# Visit a function: ignore if non main!
|
||||
def visitFuncDef(self, ctx) -> None:
|
||||
funname = ctx.ID().getText()
|
||||
if funname == "main":
|
||||
self.has_main = True
|
||||
self.visit(ctx.vardecl_l())
|
||||
self.visit(ctx.block())
|
||||
147
MiniC/TP03/MiniCTypingVisitor.py
Normal file
147
MiniC/TP03/MiniCTypingVisitor.py
Normal file
@ -0,0 +1,147 @@
|
||||
# Visitor to *typecheck* MiniC files
|
||||
from typing import List
|
||||
from MiniCVisitor import MiniCVisitor
|
||||
from MiniCParser import MiniCParser
|
||||
from Lib.Errors import MiniCInternalError, MiniCTypeError
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class BaseType(Enum):
|
||||
Float, Integer, Boolean, String = range(4)
|
||||
|
||||
|
||||
# Basic Type Checking for MiniC programs.
|
||||
class MiniCTypingVisitor(MiniCVisitor):
|
||||
|
||||
def __init__(self):
|
||||
self._memorytypes = dict() # id -> types
|
||||
# For now, we don't have real functions ...
|
||||
self._current_function = "main"
|
||||
|
||||
def _raise(self, ctx, for_what, *types):
|
||||
raise MiniCTypeError(
|
||||
'In function {}: Line {} col {}: invalid type for {}: {}'.format(
|
||||
self._current_function,
|
||||
ctx.start.line, ctx.start.column, for_what,
|
||||
' and '.join(t.name.lower() for t in types)))
|
||||
|
||||
def _assertSameType(self, ctx, for_what, *types):
|
||||
if not all(types[0] == t for t in types):
|
||||
raise MiniCTypeError(
|
||||
'In function {}: Line {} col {}: type mismatch for {}: {}'.format(
|
||||
self._current_function,
|
||||
ctx.start.line, ctx.start.column, for_what,
|
||||
' and '.join(t.name.lower() for t in types)))
|
||||
|
||||
def _raiseNonType(self, ctx, message):
|
||||
raise MiniCTypeError(
|
||||
'In function {}: Line {} col {}: {}'.format(
|
||||
self._current_function,
|
||||
ctx.start.line, ctx.start.column, message))
|
||||
|
||||
# type declaration
|
||||
|
||||
def visitVarDecl(self, ctx) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitBasicType(self, ctx):
|
||||
assert ctx.mytype is not None
|
||||
if ctx.mytype.type == MiniCParser.INTTYPE:
|
||||
return BaseType.Integer
|
||||
elif ctx.mytype.type == MiniCParser.FLOATTYPE:
|
||||
return BaseType.Float
|
||||
else: # TODO: same for other types
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitIdList(self, ctx) -> List[str]:
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitIdListBase(self, ctx) -> List[str]:
|
||||
raise NotImplementedError()
|
||||
|
||||
# typing visitors for expressions, statements !
|
||||
|
||||
# visitors for atoms --> type
|
||||
def visitParExpr(self, ctx):
|
||||
return self.visit(ctx.expr())
|
||||
|
||||
def visitIntAtom(self, ctx):
|
||||
return BaseType.Integer
|
||||
|
||||
def visitFloatAtom(self, ctx):
|
||||
return BaseType.Float
|
||||
|
||||
def visitBooleanAtom(self, ctx):
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitIdAtom(self, ctx):
|
||||
try:
|
||||
return self._memorytypes[ctx.getText()]
|
||||
except KeyError:
|
||||
self._raiseNonType(ctx,
|
||||
"Undefined variable {}".format(ctx.getText()))
|
||||
|
||||
def visitStringAtom(self, ctx):
|
||||
return BaseType.String
|
||||
|
||||
# now visit expr
|
||||
|
||||
def visitAtomExpr(self, ctx):
|
||||
return self.visit(ctx.atom())
|
||||
|
||||
def visitOrExpr(self, ctx):
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitAndExpr(self, ctx):
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitEqualityExpr(self, ctx):
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitRelationalExpr(self, ctx):
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitAdditiveExpr(self, ctx):
|
||||
assert ctx.myop is not None
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitMultiplicativeExpr(self, ctx):
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitNotExpr(self, ctx):
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitUnaryMinusExpr(self, ctx):
|
||||
raise NotImplementedError()
|
||||
|
||||
# visit statements
|
||||
|
||||
def visitPrintlnintStat(self, ctx):
|
||||
etype = self.visit(ctx.expr())
|
||||
if etype != BaseType.Integer:
|
||||
self._raise(ctx, 'println_int statement', etype)
|
||||
|
||||
def visitPrintlnfloatStat(self, ctx):
|
||||
etype = self.visit(ctx.expr())
|
||||
if etype != BaseType.Float:
|
||||
self._raise(ctx, 'println_float statement', etype)
|
||||
|
||||
def visitPrintlnboolStat(self, ctx):
|
||||
etype = self.visit(ctx.expr())
|
||||
if etype != BaseType.Boolean:
|
||||
self._raise(ctx, 'println_int statement', etype)
|
||||
|
||||
def visitPrintlnstringStat(self, ctx):
|
||||
etype = self.visit(ctx.expr())
|
||||
if etype != BaseType.String:
|
||||
self._raise(ctx, 'println_string statement', etype)
|
||||
|
||||
def visitAssignStat(self, ctx):
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitWhileStat(self, ctx):
|
||||
raise NotImplementedError()
|
||||
|
||||
def visitIfStat(self, ctx):
|
||||
raise NotImplementedError()
|
||||
13
MiniC/TP03/tests/provided/examples-types/bad_def01.c
Normal file
13
MiniC/TP03/tests/provided/examples-types/bad_def01.c
Normal file
@ -0,0 +1,13 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
int n;
|
||||
n=17;
|
||||
m=n+3;
|
||||
println_int(m);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// EXITCODE 2
|
||||
// In function main: Line 6 col 2: Undefined variable m
|
||||
10
MiniC/TP03/tests/provided/examples-types/bad_type00.c
Normal file
10
MiniC/TP03/tests/provided/examples-types/bad_type00.c
Normal file
@ -0,0 +1,10 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
int x;
|
||||
x="blablabla";
|
||||
return 0;
|
||||
}
|
||||
// EXITCODE 2
|
||||
// EXPECTED
|
||||
// In function main: Line 5 col 2: type mismatch for x: integer and string
|
||||
14
MiniC/TP03/tests/provided/examples-types/bad_type01.c
Normal file
14
MiniC/TP03/tests/provided/examples-types/bad_type01.c
Normal file
@ -0,0 +1,14 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
int n;
|
||||
string s;
|
||||
n=17;
|
||||
s="seventeen";
|
||||
s = n*s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXITCODE 2
|
||||
// EXPECTED
|
||||
// In function main: Line 8 col 6: invalid type for multiplicative operands: integer and string
|
||||
11
MiniC/TP03/tests/provided/examples-types/bad_type02.c
Normal file
11
MiniC/TP03/tests/provided/examples-types/bad_type02.c
Normal file
@ -0,0 +1,11 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
string x;
|
||||
x=1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXITCODE 2
|
||||
// EXPECTED
|
||||
// In function main: Line 5 col 2: type mismatch for x: string and integer
|
||||
10
MiniC/TP03/tests/provided/examples-types/bad_type03.c
Normal file
10
MiniC/TP03/tests/provided/examples-types/bad_type03.c
Normal file
@ -0,0 +1,10 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
int x;
|
||||
x=34+f;
|
||||
return 0;
|
||||
}
|
||||
// EXITCODE 2
|
||||
// EXPECTED
|
||||
// In function main: Line 5 col 7: Undefined variable f
|
||||
11
MiniC/TP03/tests/provided/examples-types/bad_type04.c
Normal file
11
MiniC/TP03/tests/provided/examples-types/bad_type04.c
Normal file
@ -0,0 +1,11 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
println_int("foo");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXITCODE 2
|
||||
// EXPECTED
|
||||
// In function main: Line 4 col 2: invalid type for println_int statement: string
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
println_int(true+true);
|
||||
return 0;
|
||||
}
|
||||
// EXITCODE 2
|
||||
// EXPECTED
|
||||
// In function main: Line 4 col 14: invalid type for additive operands: boolean and boolean
|
||||
12
MiniC/TP03/tests/provided/examples-types/double_decl00.c
Normal file
12
MiniC/TP03/tests/provided/examples-types/double_decl00.c
Normal file
@ -0,0 +1,12 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
int x,y;
|
||||
int z,x;
|
||||
x=42;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXITCODE 2
|
||||
// EXPECTED
|
||||
// In function main: Line 5 col 2: Variable x already declared
|
||||
11
MiniC/TP03/tests/provided/examples/bad_main.c
Normal file
11
MiniC/TP03/tests/provided/examples/bad_main.c
Normal file
@ -0,0 +1,11 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int toto(){
|
||||
println_int(42);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXITCODE 1
|
||||
// EXPECTED
|
||||
// No main function in file
|
||||
|
||||
13
MiniC/TP03/tests/provided/examples/test_assign.c
Normal file
13
MiniC/TP03/tests/provided/examples/test_assign.c
Normal file
@ -0,0 +1,13 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
int n,u;
|
||||
n=17;
|
||||
u=n;
|
||||
println_int(n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 17
|
||||
47
MiniC/TP03/tests/provided/examples/test_compare.c
Normal file
47
MiniC/TP03/tests/provided/examples/test_compare.c
Normal file
@ -0,0 +1,47 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
if (2 < 3)
|
||||
{
|
||||
println_int(1);
|
||||
}
|
||||
if (2 > 3)
|
||||
{
|
||||
println_int(2);
|
||||
}
|
||||
if (2 <= 2)
|
||||
{
|
||||
println_int(3);
|
||||
}
|
||||
if (2 >= 2)
|
||||
{
|
||||
println_int(4);
|
||||
}
|
||||
|
||||
if (2 == 3)
|
||||
{
|
||||
println_int(10);
|
||||
}
|
||||
if (2 != 3)
|
||||
{
|
||||
println_int(20);
|
||||
}
|
||||
if (2 == 2)
|
||||
{
|
||||
println_int(30);
|
||||
}
|
||||
if (2 != 2)
|
||||
{
|
||||
println_int(40);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 1
|
||||
// 3
|
||||
// 4
|
||||
// 20
|
||||
// 30
|
||||
11
MiniC/TP03/tests/provided/examples/test_expr.c
Normal file
11
MiniC/TP03/tests/provided/examples/test_expr.c
Normal file
@ -0,0 +1,11 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
if ((1.0 + 2.0) * 3.0 == 9.0) {
|
||||
println_string("OK");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// OK
|
||||
15
MiniC/TP03/tests/provided/examples/test_print.c
Normal file
15
MiniC/TP03/tests/provided/examples/test_print.c
Normal file
@ -0,0 +1,15 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
println_int(3/2+45*(2/1));
|
||||
println_int(23+19);
|
||||
println_bool( (false || 3 != 77 ) && (42<=1515) );
|
||||
println_string("coucou");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 91
|
||||
// 42
|
||||
// 1
|
||||
// coucou
|
||||
9
MiniC/TP03/tests/provided/examples/test_print_int.c
Normal file
9
MiniC/TP03/tests/provided/examples/test_print_int.c
Normal file
@ -0,0 +1,9 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
println_int(42);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 42
|
||||
11
MiniC/TP03/tests/provided/examples/test_str_assign.c
Normal file
11
MiniC/TP03/tests/provided/examples/test_str_assign.c
Normal file
@ -0,0 +1,11 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
int x;
|
||||
x="blabla";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// In function main: Line 5 col 2: type mismatch for x: integer and string
|
||||
// EXITCODE 2
|
||||
14
MiniC/TP03/tests/provided/strcat/test_string01.c
Normal file
14
MiniC/TP03/tests/provided/strcat/test_string01.c
Normal file
@ -0,0 +1,14 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
string x,y,z;
|
||||
x = "ENS";
|
||||
y = "De Lyon";
|
||||
z = x + " " + y;
|
||||
println_string(z);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// ENS De Lyon
|
||||
|
||||
16
MiniC/TP03/tests/provided/strcat/test_string02.c
Normal file
16
MiniC/TP03/tests/provided/strcat/test_string02.c
Normal file
@ -0,0 +1,16 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main(){
|
||||
string n,m;
|
||||
n = "foo";
|
||||
m = "bar";
|
||||
println_string(n);
|
||||
println_string(m);
|
||||
println_string(n + m);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// foo
|
||||
// bar
|
||||
// foobar
|
||||
13
MiniC/TP03/tests/provided/strcat/unititialized_str.c
Normal file
13
MiniC/TP03/tests/provided/strcat/unititialized_str.c
Normal file
@ -0,0 +1,13 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
string s;
|
||||
println_string(s);
|
||||
s = s + "Coucou";
|
||||
println_string(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
//
|
||||
// Coucou
|
||||
16
MiniC/TP03/tests/provided/uninitialised/bool.c
Normal file
16
MiniC/TP03/tests/provided/uninitialised/bool.c
Normal file
@ -0,0 +1,16 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
bool x;
|
||||
if (x) {
|
||||
println_int(1);
|
||||
}
|
||||
x = !x;
|
||||
if (x) {
|
||||
println_int(2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 2
|
||||
13
MiniC/TP03/tests/provided/uninitialised/float.c
Normal file
13
MiniC/TP03/tests/provided/uninitialised/float.c
Normal file
@ -0,0 +1,13 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
float x;
|
||||
println_float(x);
|
||||
x = x + 1.0;
|
||||
println_float(x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 0.00
|
||||
// 1.00
|
||||
13
MiniC/TP03/tests/provided/uninitialised/int.c
Normal file
13
MiniC/TP03/tests/provided/uninitialised/int.c
Normal file
@ -0,0 +1,13 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
int x;
|
||||
println_int(x);
|
||||
x = x + 1;
|
||||
println_int(x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 0
|
||||
// 1
|
||||
1
MiniC/TP03/tests/students/README.md
Normal file
1
MiniC/TP03/tests/students/README.md
Normal file
@ -0,0 +1 @@
|
||||
Add your own tests in this directory.
|
||||
20
MiniC/printlib.h
Normal file
20
MiniC/printlib.h
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Compatibility layer with C (meant to be #included in MiniC source
|
||||
* files). Defines types, constants and functions that are built-in
|
||||
* MiniC, to allow compiling MiniC programs with GCC.
|
||||
*/
|
||||
|
||||
typedef char * string;
|
||||
typedef int bool;
|
||||
static const int true = 1;
|
||||
static const int false = 0;
|
||||
|
||||
void print_int(int);
|
||||
void println_int(int);
|
||||
void println_bool(int);
|
||||
|
||||
void print_float(float);
|
||||
void println_float(float);
|
||||
|
||||
void print_string(string);
|
||||
void println_string(string);
|
||||
2
MiniC/pyproject.toml
Normal file
2
MiniC/pyproject.toml
Normal file
@ -0,0 +1,2 @@
|
||||
[tool.pyright]
|
||||
ignore = ["replace_cfgssa.py", "replace_smart_alloc.py", "replace_target_api.py", "test_replace_mem.py", "MiniCCWrapper.py", "MiniCCWrapperCFGSSA.py", "MiniCCWrapperSmartAlloc.py", "MiniCCWrapperFunctions.py", "MiniCLexer.py", "MiniCParser.py", "MiniCVisitor.py", "*.diff.py", "**/*.diff.py"]
|
||||
191
MiniC/test_expect_pragma.py
Normal file
191
MiniC/test_expect_pragma.py
Normal file
@ -0,0 +1,191 @@
|
||||
import collections
|
||||
import re
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
testinfo = collections.namedtuple(
|
||||
'testinfo',
|
||||
['exitcode', 'execcode', 'output', 'linkargs', 'skip_test_expected'])
|
||||
default_testinfo = testinfo(
|
||||
exitcode=0, execcode=0, output='', linkargs=[], skip_test_expected=False)
|
||||
|
||||
|
||||
def cat(filename):
|
||||
with open(filename, "rb") as f:
|
||||
for line in f:
|
||||
sys.stdout.buffer.write(line)
|
||||
|
||||
|
||||
def env_bool_variable(name, globals):
|
||||
if name not in globals:
|
||||
globals[name] = False
|
||||
if name in os.environ:
|
||||
globals[name] = True
|
||||
|
||||
|
||||
def env_str_variable(name, globals):
|
||||
if name in os.environ:
|
||||
globals[name] = os.environ[name]
|
||||
|
||||
|
||||
class TestExpectPragmas(object):
|
||||
"""Base class for tests that read the expected result as annotations
|
||||
in test files.
|
||||
|
||||
get_expect(file) will parse the file, looking EXPECT and EXITCODE
|
||||
pragmas.
|
||||
|
||||
run_command(command) is a wrapper around subprocess.check_output()
|
||||
that extracts the output and exit code.
|
||||
|
||||
"""
|
||||
|
||||
def get_expect(self, filename):
|
||||
"""Parse "filename" looking for EXPECT and EXITCODE annotations.
|
||||
|
||||
Look for a line "EXPECTED" (possibly with whitespaces and
|
||||
comments). Text after this "EXPECTED" line is the expected
|
||||
output.
|
||||
|
||||
The file may also contain a line like "EXITCODE <n>" where <n>
|
||||
is an integer, and is the expected exitcode of the command.
|
||||
|
||||
The result is cached to avoid re-parsing the file multiple
|
||||
times.
|
||||
"""
|
||||
if filename not in self.__expect:
|
||||
self.__expect[filename] = self._extract_expect(filename)
|
||||
return self.__expect[filename]
|
||||
|
||||
def remove(self, file):
|
||||
"""Like os.remove(), but ignore errors, e.g. don't complain if the
|
||||
file doesn't exist.
|
||||
"""
|
||||
try:
|
||||
os.remove(file)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def run_command(self, cmd, scope="compile"):
|
||||
"""Run the command cmd (given as [command, arg1, arg2, ...]), and
|
||||
return testinfo(exitcode=..., output=...) containing the
|
||||
exit code of the command it its standard output + standard error.
|
||||
|
||||
If scope="compile" (resp. "runtime"), then the exitcode (resp.
|
||||
execcode) is set with the exit status of the command, and the
|
||||
execcode (resp. exitcode) is set to 0.
|
||||
"""
|
||||
try:
|
||||
output = subprocess.check_output(cmd, timeout=60,
|
||||
stderr=subprocess.STDOUT)
|
||||
status = 0
|
||||
except subprocess.CalledProcessError as e:
|
||||
output = e.output
|
||||
status = e.returncode
|
||||
if scope == "runtime":
|
||||
return default_testinfo._replace(execcode=status,
|
||||
output=output.decode())
|
||||
else:
|
||||
return default_testinfo._replace(exitcode=status,
|
||||
output=output.decode())
|
||||
|
||||
def skip_if_partial_match(self, actual, expect, ignore_error_message):
|
||||
if not ignore_error_message:
|
||||
return False
|
||||
|
||||
# TODO: Deal with undefined behavior here?
|
||||
|
||||
if expect.exitcode != actual.exitcode:
|
||||
# Not the same exit code => something's wrong anyway
|
||||
return False
|
||||
|
||||
if actual.exitcode == 3:
|
||||
# There's a syntax error in both expected and actual,
|
||||
# but the actual error may slightly differ if we don't
|
||||
# have the exact same .g4.
|
||||
return True
|
||||
|
||||
# Let the test pass with 'return True' if appropriate.
|
||||
# Otherwise, continue to the full assertion for a
|
||||
# complete diagnostic.
|
||||
if actual.exitcode != 0 and expect.exitcode == actual.exitcode:
|
||||
if expect.output == '':
|
||||
# No output expected, but we know there must be an
|
||||
# error. If there was a particular error message
|
||||
# expected, we'd have written it in the output,
|
||||
# hence just ignore the actual message.
|
||||
return True
|
||||
# Ignore difference in error message except in the
|
||||
# line number (ignore the column too, it may
|
||||
# slightly vary, eg. in "foo" / 4, the error may
|
||||
# be considered on "foo" or on /):
|
||||
if re.match(r'^In function [^ :]*: Line [0-9]* col [0-9]*:', actual.output):
|
||||
out_loc = re.sub(r' col [0-9]*:.*$', '', actual.output)
|
||||
exp_loc = re.sub(r' col [0-9]*:.*$', '', expect.output)
|
||||
if out_loc == exp_loc:
|
||||
return True
|
||||
if any(x.output and (x.output.endswith('has no value yet!' + os.linesep)
|
||||
or x.output.endswith(' by 0' + os.linesep))
|
||||
for x in (actual, expect)):
|
||||
# Ignore the error message when our compiler
|
||||
# raises this error (either in actual or expect,
|
||||
# depending on what we're testing).
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
__expect = {}
|
||||
|
||||
def _extract_expect(self, file):
|
||||
exitcode = 0
|
||||
execcode = 0
|
||||
linkargs = []
|
||||
inside_expected = False
|
||||
skip_test_expected = False
|
||||
expected_lines = []
|
||||
expected_present = False
|
||||
with open(file, encoding="utf-8") as f:
|
||||
for line in f.readlines():
|
||||
# Ignore non-comments
|
||||
if not re.match(r'\s*//', line):
|
||||
continue
|
||||
# Cleanup comment start and whitespaces
|
||||
line = re.sub(r'\s*//\s*', '', line)
|
||||
line = re.sub(r'\s*$', '', line)
|
||||
|
||||
if line == 'END EXPECTED':
|
||||
inside_expected = False
|
||||
elif line.startswith('EXITCODE'):
|
||||
words = line.split(' ')
|
||||
assert len(words) == 2
|
||||
exitcode = int(words[1])
|
||||
elif line.startswith('EXECCODE'):
|
||||
words = line.split(' ')
|
||||
assert len(words) == 2
|
||||
execcode = int(words[1])
|
||||
elif line.startswith('LINKARGS'):
|
||||
words = line.split(' ')
|
||||
assert len(words) >= 2
|
||||
linkargs += [w.replace("$dir", os.path.dirname(file))
|
||||
for w in words[1:]]
|
||||
elif line == 'EXPECTED':
|
||||
inside_expected = True
|
||||
expected_present = True
|
||||
elif line == 'SKIP TEST EXPECTED':
|
||||
skip_test_expected = True
|
||||
elif inside_expected:
|
||||
expected_lines.append(line)
|
||||
|
||||
if not expected_present:
|
||||
pytest.fail("Missing EXPECTED directive in test file")
|
||||
|
||||
if expected_lines == []:
|
||||
output = ''
|
||||
else:
|
||||
output = os.linesep.join(expected_lines) + os.linesep
|
||||
|
||||
return testinfo(exitcode=exitcode, execcode=execcode,
|
||||
output=output, linkargs=linkargs,
|
||||
skip_test_expected=skip_test_expected)
|
||||
57
MiniC/test_interpreter.py
Normal file
57
MiniC/test_interpreter.py
Normal file
@ -0,0 +1,57 @@
|
||||
#! /usr/bin/env python3
|
||||
import pytest
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
from test_expect_pragma import TestExpectPragmas, cat
|
||||
|
||||
HERE = os.path.dirname(os.path.realpath(__file__))
|
||||
if HERE == os.path.realpath('.'):
|
||||
HERE = '.'
|
||||
TEST_DIR = HERE
|
||||
IMPLEM_DIR = HERE
|
||||
|
||||
|
||||
DISABLE_TYPECHECK = False # True to skip typechecking
|
||||
|
||||
ALL_FILES = []
|
||||
# tests for typing AND evaluation
|
||||
ALL_FILES += glob.glob(os.path.join(TEST_DIR, 'TP03/tests/provided/**/*.c'), recursive=True)
|
||||
ALL_FILES += glob.glob(os.path.join(TEST_DIR, 'TP03/tests/students/**/*.c'), recursive=True)
|
||||
|
||||
|
||||
# Path setting
|
||||
if 'TEST_FILES' in os.environ:
|
||||
ALL_FILES = glob.glob(os.environ['TEST_FILES'], recursive=True)
|
||||
MINIC_EVAL = os.path.join(IMPLEM_DIR, 'MiniCC.py')
|
||||
|
||||
|
||||
class TestInterpret(TestExpectPragmas):
|
||||
|
||||
def evaluate(self, file):
|
||||
if not DISABLE_TYPECHECK:
|
||||
return self.run_command([sys.executable, MINIC_EVAL, "--mode", "eval", file])
|
||||
else:
|
||||
return self.run_command([sys.executable, MINIC_EVAL, "--mode", "eval", "--disable-typecheck", file])
|
||||
|
||||
# Not in test_expect_pragma to get assertion rewritting
|
||||
def assert_equal(self, actual, expected):
|
||||
if expected.output is not None and actual.output is not None:
|
||||
assert actual.output == expected.output, \
|
||||
"Output of the program is incorrect."
|
||||
assert actual.exitcode == expected.exitcode, \
|
||||
"Exit code of the compiler is incorrect"
|
||||
assert actual.execcode == expected.execcode, \
|
||||
"Exit code of the execution is incorrect"
|
||||
|
||||
@pytest.mark.parametrize('filename', ALL_FILES)
|
||||
def test_eval(self, filename):
|
||||
cat(filename) # For diagnosis
|
||||
expect = self.get_expect(filename)
|
||||
eval = self.evaluate(filename)
|
||||
if expect:
|
||||
self.assert_equal(eval, expect)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(sys.argv)
|
||||
@ -44,3 +44,11 @@ _Academic first semester 2022-2023_
|
||||
- :book: 4th Course session: Friday 23/09/2022, 10:15. Amphi B (Ludovic Henrio)
|
||||
|
||||
* Typing [slides in english](course/CAP_cours04_typing.pdf).
|
||||
|
||||
# Week 4:
|
||||
|
||||
- :hammer: Lab 3: Wednesday 28/09/2021, 10h15-12h15. Rooms A1 (Nicolas Chappe) & B2 (Rémi Di Guardia)
|
||||
|
||||
* Interpreter & Typer [TP03](TP03/tp3.pdf).
|
||||
* Code in [TP03/](TP03/) and [MiniC/TP03/](MiniC/TP03/).
|
||||
* Graded lab due on https://etudes.ens-lyon.fr/course/view.php?id=5249 before 2022-10-04 23:59
|
||||
|
||||
2
TP03/arith-visitor/.gitignore
vendored
Normal file
2
TP03/arith-visitor/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/AritListener.py
|
||||
/AritVisitor.py
|
||||
33
TP03/arith-visitor/Arit.g4
Normal file
33
TP03/arith-visitor/Arit.g4
Normal file
@ -0,0 +1,33 @@
|
||||
grammar Arit;
|
||||
|
||||
prog: statement+ EOF #statementList
|
||||
;
|
||||
|
||||
statement
|
||||
: expr SCOL #exprInstr
|
||||
| 'set' ID '=' expr SCOL #assignInstr
|
||||
;
|
||||
|
||||
expr: expr multop=(MULT | DIV) expr #multiplicationExpr
|
||||
| expr addop=(PLUS | MINUS) expr #additiveExpr
|
||||
| atom #atomExpr
|
||||
;
|
||||
|
||||
atom: INT #numberAtom
|
||||
| ID #idAtom
|
||||
| '(' expr ')' #parens
|
||||
;
|
||||
|
||||
|
||||
SCOL : ';';
|
||||
PLUS : '+';
|
||||
MINUS : '-';
|
||||
MULT : '*';
|
||||
DIV : '/';
|
||||
ID: [a-zA-Z_] [a-zA-Z_0-9]*;
|
||||
|
||||
INT: [0-9]+;
|
||||
|
||||
COMMENT: '#' ~[\r\n]* -> skip;
|
||||
NEWLINE: '\r'? '\n' -> skip;
|
||||
WS : (' '|'\t')+ -> skip;
|
||||
23
TP03/arith-visitor/Makefile
Normal file
23
TP03/arith-visitor/Makefile
Normal file
@ -0,0 +1,23 @@
|
||||
PACKAGE = Arit
|
||||
MAINFILE = arit
|
||||
|
||||
ifndef ANTLR4
|
||||
abort:
|
||||
$(error variable ANTLR4 is not set)
|
||||
endif
|
||||
|
||||
all: $(PACKAGE).g4
|
||||
$(ANTLR4) $^ -Dlanguage=Python3 -visitor
|
||||
|
||||
run: $(MAINFILE).py
|
||||
python3 $^
|
||||
|
||||
ex: $(MAINFILE).py
|
||||
python3 $^ < myexample
|
||||
|
||||
test: all
|
||||
python3 ./test_arith_visitor.py
|
||||
|
||||
clean:
|
||||
find . \( -iname "~" -or -iname "*.cache*" -or -iname "*.diff" -or -iname "log.txt" -or -iname "*.pyc" -or -iname "*.tokens" -or -iname "*.interp" \) -exec rm -rf '{}' \;
|
||||
rm -rf $(PACKAGE)*.py
|
||||
58
TP03/arith-visitor/MyAritVisitor.py
Normal file
58
TP03/arith-visitor/MyAritVisitor.py
Normal file
@ -0,0 +1,58 @@
|
||||
from AritVisitor import AritVisitor
|
||||
from AritParser import AritParser
|
||||
|
||||
|
||||
class UnknownIdentifier(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MyAritVisitor(AritVisitor):
|
||||
"""Visitor that evaluates an expression. Derives and overrides methods
|
||||
from ArithVisitor (generated by ANTLR4)."""
|
||||
def __init__(self):
|
||||
self._memory = dict() # store id -> values
|
||||
|
||||
def visitNumberAtom(self, ctx):
|
||||
try:
|
||||
value = int(ctx.getText())
|
||||
return value
|
||||
except ValueError:
|
||||
return float(ctx.getText())
|
||||
|
||||
def visitIdAtom(self, ctx):
|
||||
try:
|
||||
return self._memory[ctx.getText()]
|
||||
except KeyError:
|
||||
raise UnknownIdentifier(ctx.getText())
|
||||
|
||||
def visitMultiplicationExpr(self, ctx):
|
||||
# Recursive calls to children. The visitor will choose the
|
||||
# appropriate method (visitSomething) automatically.
|
||||
leftval = self.visit(ctx.expr(0))
|
||||
rightval = self.visit(ctx.expr(1))
|
||||
# an elegant way to match the token:
|
||||
if ctx.multop.type == AritParser.MULT:
|
||||
return leftval * rightval
|
||||
else:
|
||||
return leftval / rightval
|
||||
|
||||
def visitAdditiveExpr(self, ctx):
|
||||
leftval = self.visit(ctx.expr(0))
|
||||
rightval = self.visit(ctx.expr(1))
|
||||
if ctx.addop.type == AritParser.PLUS:
|
||||
return leftval + rightval
|
||||
else:
|
||||
return leftval - rightval
|
||||
|
||||
def visitExprInstr(self, ctx):
|
||||
val = self.visit(ctx.expr())
|
||||
print('The value is ' + str(val))
|
||||
|
||||
def visitParens(self, ctx):
|
||||
return self.visit(ctx.expr())
|
||||
|
||||
def visitAssignInstr(self, ctx):
|
||||
val = self.visit(ctx.expr())
|
||||
name = ctx.ID().getText()
|
||||
print('now ' + name + ' has value ' + str(val))
|
||||
self._memory[name] = val
|
||||
28
TP03/arith-visitor/arit.py
Normal file
28
TP03/arith-visitor/arit.py
Normal file
@ -0,0 +1,28 @@
|
||||
from AritLexer import AritLexer
|
||||
from AritParser import AritParser
|
||||
# from AritVisitor import AritVisitor
|
||||
from MyAritVisitor import MyAritVisitor, UnknownIdentifier
|
||||
|
||||
from antlr4 import InputStream, CommonTokenStream
|
||||
import sys
|
||||
|
||||
# example of use of visitors to parse arithmetic expressions.
|
||||
# stops when the first SyntaxError is launched.
|
||||
|
||||
|
||||
def main():
|
||||
lexer = AritLexer(InputStream(sys.stdin.read()))
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = AritParser(stream)
|
||||
tree = parser.prog()
|
||||
print("Parsing : done.")
|
||||
visitor = MyAritVisitor()
|
||||
try:
|
||||
visitor.visit(tree)
|
||||
except UnknownIdentifier as exc:
|
||||
print('Unknown identifier: {}'.format(exc.args[0]))
|
||||
exit(-1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
5
TP03/arith-visitor/myexample
Normal file
5
TP03/arith-visitor/myexample
Normal file
@ -0,0 +1,5 @@
|
||||
1 ;
|
||||
12 ;
|
||||
1+2 ;
|
||||
1+2*3+4;
|
||||
(1+2)*(3+4);
|
||||
36
TP03/arith-visitor/test_arith_visitor.py
Normal file
36
TP03/arith-visitor/test_arith_visitor.py
Normal file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python
|
||||
from AritLexer import AritLexer
|
||||
from AritParser import AritParser
|
||||
import pytest
|
||||
from MyAritVisitor import MyAritVisitor
|
||||
|
||||
from antlr4 import InputStream, CommonTokenStream
|
||||
import sys
|
||||
|
||||
|
||||
@pytest.mark.parametrize("input, expected", [
|
||||
pytest.param('1+1;', 2),
|
||||
pytest.param('2-1;', 1),
|
||||
pytest.param('2*3;', 6),
|
||||
pytest.param('6/2;', 3),
|
||||
pytest.param('set x=42; x+1;', 43),
|
||||
pytest.param('set x=42; set x=12; x+1;', 13)
|
||||
])
|
||||
def test_expr(input, expected):
|
||||
lexer = AritLexer(InputStream(input))
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = AritParser(stream)
|
||||
tree = parser.prog()
|
||||
print("Parsing : done.")
|
||||
visitor = MyAritVisitor()
|
||||
|
||||
def patched_visit(self, ctx):
|
||||
self.last_expr = self.visit(ctx.expr())
|
||||
|
||||
visitor.visitExprInstr = patched_visit.__get__(visitor)
|
||||
visitor.visit(tree)
|
||||
assert visitor.last_expr == expected
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(sys.argv)
|
||||
BIN
TP03/tp3.pdf
Normal file
BIN
TP03/tp3.pdf
Normal file
Binary file not shown.
16
TP03/tree/Tree.g4
Normal file
16
TP03/tree/Tree.g4
Normal file
@ -0,0 +1,16 @@
|
||||
grammar Tree;
|
||||
|
||||
|
||||
int_tree_top : int_tree EOF #top
|
||||
;
|
||||
|
||||
int_tree: INT #leaf
|
||||
| '(' INT int_tree+ ')' #node
|
||||
;
|
||||
|
||||
|
||||
INT: [0-9]+;
|
||||
WS : (' '|'\t'|'\n')+ -> skip;
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user