Ajout TP3
This commit is contained in:
parent
3ccd91c1ec
commit
390ca18660
11
MiniC/.coveragerc
Normal file
11
MiniC/.coveragerc
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[report]
|
||||||
|
exclude_lines =
|
||||||
|
pragma: no cover
|
||||||
|
raise MiniCInternalError
|
||||||
|
raise MiniCUnsupportedError
|
||||||
|
if debug:
|
||||||
|
if debug_graphs:
|
||||||
|
if self._debug:
|
||||||
|
if self.debug:
|
||||||
|
if self._debug_graphs:
|
||||||
|
if self.debug_graphs:
|
20
MiniC/.gitignore
vendored
Normal file
20
MiniC/.gitignore
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/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
|
||||||
|
log-*.txt
|
||||||
|
.eval-done.txt
|
||||||
|
*.py.diff
|
||||||
|
*.g4.diff
|
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
|
123
MiniC/Makefile
Normal file
123
MiniC/Makefile
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
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 FILTER='TP03/**/bad*.c' test
|
||||||
|
ifdef FILTER
|
||||||
|
export FILTER
|
||||||
|
endif
|
||||||
|
|
||||||
|
# code generation mode
|
||||||
|
ifdef MODE
|
||||||
|
MINICC_OPTS+=--mode $(MODE)
|
||||||
|
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
|
||||||
|
|
||||||
|
doc: antlr
|
||||||
|
sphinx-apidoc -e -f -o doc/api . TP* replace_* *Wrapper* MiniC* conf* test*
|
||||||
|
make -C doc html
|
||||||
|
|
||||||
|
test: test-interpret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
test-pyright: antlr
|
||||||
|
pyright .
|
||||||
|
|
||||||
|
test-parse: test-pyright antlr
|
||||||
|
MINICC_OPTS="$(MINICC_OPTS) --mode=parse" python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'naive'
|
||||||
|
|
||||||
|
test-typecheck: test-pyright antlr
|
||||||
|
MINICC_OPTS="$(MINICC_OPTS) --mode=typecheck" python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'naive'
|
||||||
|
|
||||||
|
test-interpret: test-pyright test_interpreter.py main-deps
|
||||||
|
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) test_interpreter.py
|
||||||
|
|
||||||
|
|
||||||
|
ifndef MODE
|
||||||
|
# The export must be on the same line as the command (note the ';'), because
|
||||||
|
# make starts a new shell for each line.
|
||||||
|
LINEAR=export MINICC_OPTS="${MINICC_OPTS} --mode codegen-linear";
|
||||||
|
else
|
||||||
|
LINEAR=
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Test for naive allocator (also runs test_expect to check // EXPECTED directives):
|
||||||
|
test-naive: test-pyright antlr
|
||||||
|
$(LINEAR) python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'test_naive or test_expect'
|
||||||
|
|
||||||
|
test-mem: test-pyright antlr
|
||||||
|
$(LINEAR) python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'test_alloc_mem'
|
||||||
|
|
||||||
|
test-hybrid: test-pyright antlr
|
||||||
|
$(LINEAR) python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'test_alloc_hybrid'
|
||||||
|
|
||||||
|
# Test for all but the smart allocator, i.e. everything that lab4 should pass:
|
||||||
|
test-lab4: test-pyright antlr
|
||||||
|
$(LINEAR) python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'not test_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 'test_smart'
|
||||||
|
|
||||||
|
# Complete testsuite (should pass for lab5):
|
||||||
|
test-codegen: test-pyright antlr
|
||||||
|
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py
|
||||||
|
|
||||||
|
NAME_CLEAN = ${shell printf '%s' "$(MYNAME)" | tr -cd '[0-9a-zA-Z]'}
|
||||||
|
BASEDIR=$$(basename "$$PWD")
|
||||||
|
tar: clean
|
||||||
|
dir=$$(basename "$$PWD") && cd .. && \
|
||||||
|
tar cvfz "$$dir"-$(NAME_CLEAN).tgz --exclude=".git" --exclude=".pytest_cache" \
|
||||||
|
--exclude="htmlcov" "$$dir"
|
||||||
|
@echo "Created ../$$(basename "$$PWD")-$(NAME_CLEAN).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", "hybrid")):
|
||||||
|
try:
|
||||||
|
os.remove(s)
|
||||||
|
print("Removed {}".format(s))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
endef
|
||||||
|
export CLEAN
|
||||||
|
clean-tests:
|
||||||
|
@python3 -c "$$CLEAN"
|
||||||
|
find . -iname "*.riscv" -print0 | xargs -0 rm -rf \;
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
.PHONY: install-deps
|
||||||
|
install-deps:
|
||||||
|
python3 -m pip install antlr4-python3-runtime==4.13.1 pytest pytest-cov pytest-xdist coverage graphviz networkx pygraphviz
|
||||||
|
|
||||||
|
.PHONY: test test-interpret test-codegen clean clean-tests tar antlr
|
||||||
|
# multiple invocations of pytest in parallel would share temporary file names
|
||||||
|
# and sometimes break. Parallelism can be achieved within pytest with
|
||||||
|
# pytest-xdist, which is efficient and safe.
|
||||||
|
.NOTPARALLEL:
|
138
MiniC/MiniC.g4
Normal file
138
MiniC/MiniC.g4
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
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' (actually a 'return INT' because we don't have a ZERO
|
||||||
|
// lexical token).
|
||||||
|
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
|
||||||
|
;
|
||||||
|
|
333
MiniC/MiniCC.py
Executable file
333
MiniC/MiniCC.py
Executable file
@ -0,0 +1,333 @@
|
|||||||
|
#! /usr/bin/python3
|
||||||
|
"""
|
||||||
|
Evaluation and code generation labs, main file.
|
||||||
|
Usage:
|
||||||
|
python3 MiniCC.py --mode <mode> <filename>
|
||||||
|
python3 MiniCC.py --help
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import cast
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
import antlr4
|
||||||
|
from antlr4.error.ErrorListener import ErrorListener
|
||||||
|
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from traceback import print_exc
|
||||||
|
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 is_ssa(self) -> bool:
|
||||||
|
return self.value >= Mode.SSA.value
|
||||||
|
|
||||||
|
|
||||||
|
def valid_modes():
|
||||||
|
modes = ['parse', 'typecheck', 'eval']
|
||||||
|
|
||||||
|
try:
|
||||||
|
import TP04.MiniCCodeGen3AVisitor # pyright: ignore # noqa: F401, type: ignore
|
||||||
|
modes.append('codegen-linear')
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
return modes
|
||||||
|
|
||||||
|
try:
|
||||||
|
import Lib.CFG # pyright: ignore # noqa: F401, type: ignore
|
||||||
|
modes.append('codegen-cfg')
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
return modes
|
||||||
|
|
||||||
|
try:
|
||||||
|
import TP05.EnterSSA # pyright: ignore # noqa: F401, type: ignore
|
||||||
|
modes.append('codegen-ssa')
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
return modes
|
||||||
|
|
||||||
|
try:
|
||||||
|
import TPoptim.OptimSSA # pyright: ignore # noqa: F401, type: ignore
|
||||||
|
modes.append('codegen-optim')
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
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, offendingSymbol, 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, dom_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 = antlr4.FileStream(inputname, encoding='utf-8')
|
||||||
|
lexer = MiniCLexer(input_s)
|
||||||
|
counter = CountErrorListener()
|
||||||
|
lexer._listeners.append(counter)
|
||||||
|
stream = antlr4.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]
|
||||||
|
code = build_cfg(function)
|
||||||
|
if debug_graphs:
|
||||||
|
s = "{}.{}.dot".format(basename, code.fdata.get_name())
|
||||||
|
print("CFG:", s)
|
||||||
|
code.print_dot(s, view=True)
|
||||||
|
if mode.is_ssa():
|
||||||
|
from TP05.EnterSSA import enter_ssa # type: ignore[import]
|
||||||
|
from Lib.CFG import CFG # type: ignore[import]
|
||||||
|
enter_ssa(cast(CFG, code), dom_graphs, basename)
|
||||||
|
if ssa_graphs:
|
||||||
|
s = "{}.{}.enterssa.dot".format(basename, code.fdata.get_name())
|
||||||
|
print("SSA:", s)
|
||||||
|
code.print_dot(s, view=True)
|
||||||
|
if mode == Mode.OPTIM:
|
||||||
|
from TPoptim.OptimSSA import OptimSSA # type: ignore[import]
|
||||||
|
OptimSSA(cast(CFG, code), debug=debug)
|
||||||
|
if ssa_graphs:
|
||||||
|
s = "{}.{}.optimssa.dot".format(basename, code.fdata.get_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 == "hybrid":
|
||||||
|
from TP04.HybridNaiveAllocator import HybridNaiveAllocator # type: ignore[import]
|
||||||
|
allocator = HybridNaiveAllocator(fdata)
|
||||||
|
comment = "hybrid, naive allocation"
|
||||||
|
elif reg_alloc == "smart":
|
||||||
|
liveness = None
|
||||||
|
if mode.is_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.is_ssa():
|
||||||
|
from Lib.CFG import CFG # type: ignore[import]
|
||||||
|
from TP05.ExitSSA import exit_ssa # type: ignore[import]
|
||||||
|
exit_ssa(cast(CFG, code), reg_alloc == 'smart')
|
||||||
|
comment += " with SSA"
|
||||||
|
if allocator:
|
||||||
|
allocator.rewriteCode(code)
|
||||||
|
if mode.is_ssa() and ssa_graphs:
|
||||||
|
s = "{}.{}.exitssa.dot".format(basename, code.fdata.get_name())
|
||||||
|
print("CFG after SSA:", s)
|
||||||
|
code.print_dot(s, view=True)
|
||||||
|
from Lib.LinearCode import LinearCode # type: ignore[import]
|
||||||
|
output.write(f"# Code generated by {os.path.realpath(sys.argv[0])}\n")
|
||||||
|
for v in os.environ:
|
||||||
|
if v.startswith("REPLACE_"):
|
||||||
|
output.write(f"# {v}={os.environ[v]}\n")
|
||||||
|
output.write(f"# Options: {' '.join(sys.argv[1:])}\n")
|
||||||
|
if isinstance(code, LinearCode):
|
||||||
|
code.print_code(output, comment=comment)
|
||||||
|
else:
|
||||||
|
from Lib.CFG import CFG # type: ignore[import]
|
||||||
|
from TP04.LinearizeCFG import linearize # type: ignore[import]
|
||||||
|
assert (isinstance(code, CFG))
|
||||||
|
code.print_code(output, linearize=linearize, comment=comment)
|
||||||
|
if debug:
|
||||||
|
visitor3.printSymbolTable()
|
||||||
|
|
||||||
|
|
||||||
|
# command line management
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
modes = valid_modes()
|
||||||
|
|
||||||
|
parser = ArgumentParser(description='CAP/MIF08 MiniCC compiler')
|
||||||
|
|
||||||
|
parser.add_argument('filename', type=str,
|
||||||
|
help='Source file.')
|
||||||
|
parser.add_argument('--mode', type=str,
|
||||||
|
choices=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', 'hybrid', '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 the CFG at SSA entry and exit.')
|
||||||
|
parser.add_argument('--dom-graphs', action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Display dominance-related 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
|
||||||
|
dom_graphs = args.dom_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, dom_graphs)
|
||||||
|
except MiniCUnsupportedError as e:
|
||||||
|
print(e)
|
||||||
|
exit(5)
|
||||||
|
except (MiniCInternalError, AllocationError):
|
||||||
|
print_exc()
|
||||||
|
exit(4)
|
16
MiniC/MiniCLexer.pyi
Normal file
16
MiniC/MiniCLexer.pyi
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from antlr4.error.ErrorListener import ErrorListener
|
||||||
|
from antlr4.Lexer import Lexer
|
||||||
|
from typing import Callable, Any, List
|
||||||
|
|
||||||
|
class MiniCLexer(Lexer):
|
||||||
|
_listeners: List[ErrorListener]
|
||||||
|
EQ: int
|
||||||
|
LT: int
|
||||||
|
LTEQ: int
|
||||||
|
GT: int
|
||||||
|
GTEQ: int
|
||||||
|
PLUS: int
|
||||||
|
MINUS: int
|
||||||
|
MULT: int
|
||||||
|
DIV: int
|
||||||
|
MOD: int
|
22
MiniC/MiniCParser.pyi
Normal file
22
MiniC/MiniCParser.pyi
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from antlr4.error.ErrorListener import ErrorListener
|
||||||
|
from antlr4.Parser import Parser
|
||||||
|
from typing import Callable, Any, List
|
||||||
|
|
||||||
|
class MiniCParser(Parser):
|
||||||
|
_listeners: List[ErrorListener]
|
||||||
|
prog: Callable[[], Any]
|
||||||
|
EQ: int
|
||||||
|
LT: int
|
||||||
|
LTEQ: int
|
||||||
|
GT: int
|
||||||
|
GTEQ: int
|
||||||
|
PLUS: int
|
||||||
|
MINUS: int
|
||||||
|
MULT: int
|
||||||
|
DIV: int
|
||||||
|
MOD: int
|
||||||
|
NEQ: int
|
||||||
|
INTTYPE: int
|
||||||
|
FLOATTYPE: int
|
||||||
|
BOOLTYPE: int
|
||||||
|
STRINGTYPE: int
|
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 / CS444 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?
|
185
MiniC/TP03/MiniCInterpretVisitor.py
Normal file
185
MiniC/TP03/MiniCInterpretVisitor.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
# Visitor to *interpret* MiniC files
|
||||||
|
from typing import (
|
||||||
|
Dict,
|
||||||
|
List)
|
||||||
|
from MiniCVisitor import MiniCVisitor
|
||||||
|
from MiniCParser import MiniCParser
|
||||||
|
from Lib.Errors import MiniCRuntimeError, MiniCInternalError, MiniCUnsupportedError
|
||||||
|
|
||||||
|
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(f"Initialization for type {type_str}")
|
||||||
|
|
||||||
|
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(
|
||||||
|
f"Unknown comparison operator '{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(
|
||||||
|
f"Unknown additive operator '{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(
|
||||||
|
f"Unknown multiplicative operator '{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 = f"{val:.2f}"
|
||||||
|
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())
|
||||||
|
else:
|
||||||
|
raise MiniCUnsupportedError("Functions are not supported in evaluation mode")
|
||||||
|
|
||||||
|
def visitFuncCall(self, ctx) -> None: # pragma: no cover
|
||||||
|
raise MiniCUnsupportedError("Functions are not supported in evaluation mode")
|
148
MiniC/TP03/MiniCTypingVisitor.py
Normal file
148
MiniC/TP03/MiniCTypingVisitor.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# Visitor to *typecheck* MiniC files
|
||||||
|
from typing import List, NoReturn
|
||||||
|
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) -> NoReturn:
|
||||||
|
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):
|
||||||
|
assert ctx.myop is not None
|
||||||
|
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_bool 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_bool(true+true);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// EXITCODE 2
|
||||||
|
// EXPECTED
|
||||||
|
// In function main: Line 4 col 15: 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
|
12
MiniC/TP03/tests/provided/examples/bad_main.c
Normal file
12
MiniC/TP03/tests/provided/examples/bad_main.c
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#include "printlib.h"
|
||||||
|
|
||||||
|
int toto(){
|
||||||
|
println_int(42);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SKIP TEST EXPECTED
|
||||||
|
// EXITCODE 5
|
||||||
|
// EXPECTED
|
||||||
|
// Functions are not supported in evaluation mode
|
||||||
|
|
7
MiniC/TP03/tests/provided/examples/no_main.c
Normal file
7
MiniC/TP03/tests/provided/examples/no_main.c
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#include "printlib.h"
|
||||||
|
|
||||||
|
// SKIP TEST EXPECTED
|
||||||
|
// EXECCODE 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
|
33
MiniC/TP03/tests/provided/examples/test_basic_bool.c
Normal file
33
MiniC/TP03/tests/provided/examples/test_basic_bool.c
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#include "printlib.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
println_bool(1 == 1);
|
||||||
|
println_bool(1 == 2);
|
||||||
|
println_bool(1 != 1);
|
||||||
|
println_bool(1 != 2);
|
||||||
|
println_bool(1 < 2);
|
||||||
|
println_bool(1 < 0);
|
||||||
|
println_bool(1 <= 2);
|
||||||
|
println_bool(1 <= 0);
|
||||||
|
println_bool(1 > 2);
|
||||||
|
println_bool(1 > 0);
|
||||||
|
println_bool(1 >= 2);
|
||||||
|
println_bool(1 >= 0);
|
||||||
|
println_bool(!(1 < 2));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECTED
|
||||||
|
// 1
|
||||||
|
// 0
|
||||||
|
// 0
|
||||||
|
// 1
|
||||||
|
// 1
|
||||||
|
// 0
|
||||||
|
// 1
|
||||||
|
// 0
|
||||||
|
// 0
|
||||||
|
// 1
|
||||||
|
// 0
|
||||||
|
// 1
|
||||||
|
// 0
|
23
MiniC/TP03/tests/provided/examples/test_basic_expr.c
Normal file
23
MiniC/TP03/tests/provided/examples/test_basic_expr.c
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#include "printlib.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
println_int(42);
|
||||||
|
println_int(3 - 2);
|
||||||
|
println_int(-42);
|
||||||
|
println_int(- -42);
|
||||||
|
println_int(10 / 3);
|
||||||
|
println_int(9 / 3);
|
||||||
|
println_int(8 / 3);
|
||||||
|
println_float(42.3);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECTED
|
||||||
|
// 42
|
||||||
|
// 1
|
||||||
|
// -42
|
||||||
|
// 42
|
||||||
|
// 3
|
||||||
|
// 3
|
||||||
|
// 2
|
||||||
|
// 42.30
|
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
|
15
MiniC/TP03/tests/provided/strcat/test_string01.c
Normal file
15
MiniC/TP03/tests/provided/strcat/test_string01.c
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#include "printlib.h"
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
string x,y,z;
|
||||||
|
x = "ENS";
|
||||||
|
y = "De Lyon";
|
||||||
|
z = x + " " + y;
|
||||||
|
println_string(z);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SKIP TEST EXPECTED
|
||||||
|
// EXPECTED
|
||||||
|
// ENS De Lyon
|
||||||
|
|
17
MiniC/TP03/tests/provided/strcat/test_string02.c
Normal file
17
MiniC/TP03/tests/provided/strcat/test_string02.c
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#include "printlib.h"
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
string n,m;
|
||||||
|
n = "foo";
|
||||||
|
m = "bar";
|
||||||
|
println_string(n);
|
||||||
|
println_string(m);
|
||||||
|
println_string(n + m); // non-standard C
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SKIP TEST EXPECTED
|
||||||
|
// EXPECTED
|
||||||
|
// foo
|
||||||
|
// bar
|
||||||
|
// foobar
|
14
MiniC/TP03/tests/provided/strcat/unititialized_str.c
Normal file
14
MiniC/TP03/tests/provided/strcat/unititialized_str.c
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#include "printlib.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
string s;
|
||||||
|
println_string(s);
|
||||||
|
s = s + "Coucou";
|
||||||
|
println_string(s);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SKIP TEST EXPECTED
|
||||||
|
// EXPECTED
|
||||||
|
//
|
||||||
|
// Coucou
|
17
MiniC/TP03/tests/provided/uninitialised/bool.c
Normal file
17
MiniC/TP03/tests/provided/uninitialised/bool.c
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#include "printlib.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
bool x;
|
||||||
|
if (x) {
|
||||||
|
println_int(1);
|
||||||
|
}
|
||||||
|
x = !x;
|
||||||
|
if (x) {
|
||||||
|
println_int(2);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SKIP TEST EXPECTED
|
||||||
|
// 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.
|
1
MiniC/TP03/tests/students/ext-for-c/README.md
Normal file
1
MiniC/TP03/tests/students/ext-for-c/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add your own tests for the C-like 'for' loop in this directory (tests for the interpreter).
|
20
MiniC/doc/Makefile
Normal file
20
MiniC/doc/Makefile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line, and also
|
||||||
|
# from the environment for the first two.
|
||||||
|
SPHINXOPTS ?=
|
||||||
|
SPHINXBUILD ?= sphinx-build
|
||||||
|
SOURCEDIR = .
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
61
MiniC/doc/conf.py
Normal file
61
MiniC/doc/conf.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
# Configuration file for the Sphinx documentation builder.
|
||||||
|
#
|
||||||
|
# This file only contains a selection of the most common options. For a full
|
||||||
|
# list see the documentation:
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||||
|
|
||||||
|
# -- Path setup --------------------------------------------------------------
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
|
|
||||||
|
|
||||||
|
# -- Project information -----------------------------------------------------
|
||||||
|
|
||||||
|
project = 'MiniC'
|
||||||
|
copyright = '2023, compil-lyon'
|
||||||
|
author = 'compil-lyon'
|
||||||
|
|
||||||
|
|
||||||
|
# -- General configuration ---------------------------------------------------
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = [
|
||||||
|
'sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
]
|
||||||
|
|
||||||
|
autodoc_default_options = {
|
||||||
|
'member-order': 'bysource',
|
||||||
|
'members': True,
|
||||||
|
'undoc-members': True,
|
||||||
|
'ignore-module-all': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This pattern also affects html_static_path and html_extra_path.
|
||||||
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output -------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
html_theme = 'sphinx_rtd_theme'
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
77
MiniC/doc/index.rst
Normal file
77
MiniC/doc/index.rst
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
.. MiniC documentation master file, created by
|
||||||
|
sphinx-quickstart on Thu Feb 3 16:47:38 2022.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
Welcome to MiniC's documentation!
|
||||||
|
=================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
:caption: Contents:
|
||||||
|
|
||||||
|
Base library - Errors <api/Lib.Errors>
|
||||||
|
Base library - Statement <api/Lib.Statement>
|
||||||
|
Base library - RISC-V instructions <api/Lib.RiscV>
|
||||||
|
Base library - Operands <api/Lib.Operands>
|
||||||
|
Base library - Function data <api/Lib.FunctionData>
|
||||||
|
Base library - Graphs <api/Lib.Graphes>
|
||||||
|
Linear intermediate representation <api/Lib.LinearCode>
|
||||||
|
Temporary allocation <api/Lib.Allocator>
|
||||||
|
Control Flow Graph - CFG and Basic blocks <api/Lib.CFG>
|
||||||
|
Control Flow Graph - Terminators <api/Lib.Terminator>
|
||||||
|
SSA form - Dominance frontier <api/Lib.Dominators>
|
||||||
|
SSA form - Phi Nodes <api/Lib.PhiNode>
|
||||||
|
|
||||||
|
These pages document the various Python sources in the Lib/
|
||||||
|
folder of MiniC. You should not have to edit them *at all*.
|
||||||
|
|
||||||
|
Base library
|
||||||
|
------------
|
||||||
|
|
||||||
|
The :doc:`api/Lib.Statement` defines various classes that represent
|
||||||
|
RISC-V assembly statements, such as labels or 3-address instructions.
|
||||||
|
|
||||||
|
We won't create objects of these classes directly very often.
|
||||||
|
Instead, to easily create such statements for standard RISC-V assembly instructions
|
||||||
|
and pseudo-instructions, we give you the :doc:`api/Lib.RiscV`.
|
||||||
|
|
||||||
|
RISC-V instructions take arguments of various kinds,
|
||||||
|
as defined in the :doc:`api/Lib.Operands`.
|
||||||
|
|
||||||
|
At some point, we will need some basic functions about oriented and non oriented graphs,
|
||||||
|
those are present in :doc:`api/Lib.Graphes`.
|
||||||
|
|
||||||
|
Linear Intermediate representation
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
The :doc:`api/Lib.LinearCode` allows to work with assembly source code
|
||||||
|
modeled as a list of statements.
|
||||||
|
|
||||||
|
Temporary allocation
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Before implementing the all-in-memory allocator of lab 4a,
|
||||||
|
you should understand the naive allocator in the :doc:`api/Lib.Allocator`.
|
||||||
|
|
||||||
|
Control Flow Graph Intermediate representation
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
The classes for the CFG and its basic blocks are in the :doc:`api/Lib.CFG`.
|
||||||
|
Each block ends with a terminator, as documented in the :doc:`api/Lib.Terminator`.
|
||||||
|
|
||||||
|
SSA form
|
||||||
|
--------
|
||||||
|
|
||||||
|
The translation of the CFG into SSA form makes use of dominance frontiers.
|
||||||
|
Functions to work with dominance are defined in the :doc:`api/Lib.Dominators`.
|
||||||
|
|
||||||
|
Phi nodes, a special kind of statement that appears in CFGs in SSA form,
|
||||||
|
are defined in the :doc:`api/Lib.PhiNode`.
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
251
MiniC/libprint.s
Normal file
251
MiniC/libprint.s
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
.globl println_int
|
||||||
|
println_int:
|
||||||
|
.globl println_bool
|
||||||
|
println_bool:
|
||||||
|
addi sp,sp,-8
|
||||||
|
sd ra, 0(sp)
|
||||||
|
call print_int
|
||||||
|
call newline
|
||||||
|
ld ra, 0(sp)
|
||||||
|
addi sp,sp,8
|
||||||
|
ret
|
||||||
|
|
||||||
|
.globl println_char
|
||||||
|
println_char:
|
||||||
|
addi sp,sp,-8
|
||||||
|
sd ra, 0(sp)
|
||||||
|
call print_char
|
||||||
|
call newline
|
||||||
|
ld ra, 0(sp)
|
||||||
|
addi sp,sp,8
|
||||||
|
ret
|
||||||
|
|
||||||
|
.text
|
||||||
|
.align 1
|
||||||
|
.globl println_string
|
||||||
|
.type println_string, @function
|
||||||
|
println_string: #address stored in a0
|
||||||
|
addi sp,sp,-152
|
||||||
|
sd ra,24(sp)
|
||||||
|
sd s0,16(sp)
|
||||||
|
addi s0,sp,32
|
||||||
|
sd t0,32(sp)
|
||||||
|
sd t1,40(sp)
|
||||||
|
sd t2,48(sp)
|
||||||
|
sd t3,56(sp)
|
||||||
|
sd t4,64(sp)
|
||||||
|
sd t5,72(sp)
|
||||||
|
sd t6,80(sp)
|
||||||
|
|
||||||
|
sd a0,88(sp)
|
||||||
|
sd a1,96(sp)
|
||||||
|
sd a2,104(sp)
|
||||||
|
sd a3,112(sp)
|
||||||
|
sd a4,120(sp)
|
||||||
|
sd a5,128(sp)
|
||||||
|
sd a6,136(sp)
|
||||||
|
sd a7,144(sp)
|
||||||
|
|
||||||
|
## Argument is already in a0, just forward it to puts
|
||||||
|
call puts
|
||||||
|
|
||||||
|
ld ra,24(sp)
|
||||||
|
ld s0,16(sp)
|
||||||
|
|
||||||
|
ld t0,32(sp)
|
||||||
|
ld t1,40(sp)
|
||||||
|
ld t2,48(sp)
|
||||||
|
ld t3,56(sp)
|
||||||
|
ld t4,64(sp)
|
||||||
|
ld t5,72(sp)
|
||||||
|
ld t6,80(sp)
|
||||||
|
|
||||||
|
ld a0,88(sp)
|
||||||
|
ld a1,96(sp)
|
||||||
|
ld a2,104(sp)
|
||||||
|
ld a3,112(sp)
|
||||||
|
ld a4,120(sp)
|
||||||
|
ld a5,128(sp)
|
||||||
|
ld a6,136(sp)
|
||||||
|
ld a7,144(sp)
|
||||||
|
|
||||||
|
addi sp,sp,152
|
||||||
|
jr ra
|
||||||
|
.size println_string, .-println_string
|
||||||
|
.section .rodata
|
||||||
|
.align 3
|
||||||
|
fmt_int:
|
||||||
|
.string "%ld"
|
||||||
|
str_empty:
|
||||||
|
.string ""
|
||||||
|
.text
|
||||||
|
.align 1
|
||||||
|
.globl print_int
|
||||||
|
.type print_int, @function
|
||||||
|
print_int: # print int stored in a0, saves/restores all scratch registers (except ft<n> which we don't use)
|
||||||
|
addi sp,sp,-152
|
||||||
|
|
||||||
|
sd ra,24(sp)
|
||||||
|
sd s0,16(sp)
|
||||||
|
|
||||||
|
sd t0,32(sp)
|
||||||
|
sd t1,40(sp)
|
||||||
|
sd t2,48(sp)
|
||||||
|
sd t3,56(sp)
|
||||||
|
sd t4,64(sp)
|
||||||
|
sd t5,72(sp)
|
||||||
|
sd t6,80(sp)
|
||||||
|
|
||||||
|
sd a0,88(sp)
|
||||||
|
sd a1,96(sp)
|
||||||
|
sd a2,104(sp)
|
||||||
|
sd a3,112(sp)
|
||||||
|
sd a4,120(sp)
|
||||||
|
sd a5,128(sp)
|
||||||
|
sd a6,136(sp)
|
||||||
|
sd a7,144(sp)
|
||||||
|
|
||||||
|
## first parameter of print_int is second parameter of printf
|
||||||
|
mv a1,a0
|
||||||
|
## first parameter of printf is the format string
|
||||||
|
la a0,fmt_int
|
||||||
|
call printf
|
||||||
|
|
||||||
|
ld ra,24(sp)
|
||||||
|
ld s0,16(sp)
|
||||||
|
|
||||||
|
ld t0,32(sp)
|
||||||
|
ld t1,40(sp)
|
||||||
|
ld t2,48(sp)
|
||||||
|
ld t3,56(sp)
|
||||||
|
ld t4,64(sp)
|
||||||
|
ld t5,72(sp)
|
||||||
|
ld t6,80(sp)
|
||||||
|
|
||||||
|
ld a0,88(sp)
|
||||||
|
ld a1,96(sp)
|
||||||
|
ld a2,104(sp)
|
||||||
|
ld a3,112(sp)
|
||||||
|
ld a4,120(sp)
|
||||||
|
ld a5,128(sp)
|
||||||
|
ld a6,136(sp)
|
||||||
|
ld a7,144(sp)
|
||||||
|
|
||||||
|
addi sp,sp,152
|
||||||
|
jr ra
|
||||||
|
.size print_int, .-print_int
|
||||||
|
.align 1
|
||||||
|
.globl newline
|
||||||
|
.type newline, @function
|
||||||
|
newline: # print int stored in a0, saves/restores all scratch registers (except ft<n> which we don't use)
|
||||||
|
addi sp,sp,-152
|
||||||
|
|
||||||
|
sd ra,24(sp)
|
||||||
|
sd s0,16(sp)
|
||||||
|
|
||||||
|
sd t0,32(sp)
|
||||||
|
sd t1,40(sp)
|
||||||
|
sd t2,48(sp)
|
||||||
|
sd t3,56(sp)
|
||||||
|
sd t4,64(sp)
|
||||||
|
sd t5,72(sp)
|
||||||
|
sd t6,80(sp)
|
||||||
|
|
||||||
|
sd a0,88(sp)
|
||||||
|
sd a1,96(sp)
|
||||||
|
sd a2,104(sp)
|
||||||
|
sd a3,112(sp)
|
||||||
|
sd a4,120(sp)
|
||||||
|
sd a5,128(sp)
|
||||||
|
sd a6,136(sp)
|
||||||
|
sd a7,144(sp)
|
||||||
|
|
||||||
|
## first parameter of printf is the format string
|
||||||
|
la a0,str_empty
|
||||||
|
call puts
|
||||||
|
|
||||||
|
ld ra,24(sp)
|
||||||
|
ld s0,16(sp)
|
||||||
|
|
||||||
|
ld t0,32(sp)
|
||||||
|
ld t1,40(sp)
|
||||||
|
ld t2,48(sp)
|
||||||
|
ld t3,56(sp)
|
||||||
|
ld t4,64(sp)
|
||||||
|
ld t5,72(sp)
|
||||||
|
ld t6,80(sp)
|
||||||
|
|
||||||
|
ld a0,88(sp)
|
||||||
|
ld a1,96(sp)
|
||||||
|
ld a2,104(sp)
|
||||||
|
ld a3,112(sp)
|
||||||
|
ld a4,120(sp)
|
||||||
|
ld a5,128(sp)
|
||||||
|
ld a6,136(sp)
|
||||||
|
ld a7,144(sp)
|
||||||
|
|
||||||
|
addi sp,sp,152
|
||||||
|
jr ra
|
||||||
|
.size newline, .-newline
|
||||||
|
.align 1
|
||||||
|
.globl print_char
|
||||||
|
.type print_char, @function
|
||||||
|
print_char: # print char stored in a0 (ascii code)
|
||||||
|
addi sp,sp,-152
|
||||||
|
sd ra,24(sp)
|
||||||
|
sd s0,16(sp)
|
||||||
|
|
||||||
|
addi s0,sp,32
|
||||||
|
|
||||||
|
|
||||||
|
sd t0,32(sp)
|
||||||
|
sd t1,40(sp)
|
||||||
|
sd t2,48(sp)
|
||||||
|
sd t3,56(sp)
|
||||||
|
sd t4,64(sp)
|
||||||
|
sd t5,72(sp)
|
||||||
|
sd t6,80(sp)
|
||||||
|
|
||||||
|
sd a0,88(sp)
|
||||||
|
sd a1,96(sp)
|
||||||
|
sd a2,104(sp)
|
||||||
|
sd a3,112(sp)
|
||||||
|
sd a4,120(sp)
|
||||||
|
sd a5,128(sp)
|
||||||
|
sd a6,136(sp)
|
||||||
|
sd a7,144(sp)
|
||||||
|
|
||||||
|
# call to putchar
|
||||||
|
mv a5,a0
|
||||||
|
sb a5,-17(s0)
|
||||||
|
lbu a5,-17(s0)
|
||||||
|
sext.w a5,a5
|
||||||
|
mv a0,a5
|
||||||
|
call putchar
|
||||||
|
|
||||||
|
#restore registers
|
||||||
|
ld ra,24(sp)
|
||||||
|
ld s0,16(sp)
|
||||||
|
|
||||||
|
ld t0,32(sp)
|
||||||
|
ld t1,40(sp)
|
||||||
|
ld t2,48(sp)
|
||||||
|
ld t3,56(sp)
|
||||||
|
ld t4,64(sp)
|
||||||
|
ld t5,72(sp)
|
||||||
|
ld t6,80(sp)
|
||||||
|
|
||||||
|
ld a0,88(sp)
|
||||||
|
ld a1,96(sp)
|
||||||
|
ld a2,104(sp)
|
||||||
|
ld a3,112(sp)
|
||||||
|
ld a4,120(sp)
|
||||||
|
ld a5,128(sp)
|
||||||
|
ld a6,136(sp)
|
||||||
|
ld a7,144(sp)
|
||||||
|
|
||||||
|
addi sp,sp,152
|
||||||
|
jr ra
|
||||||
|
.size print_char, .-print_char
|
||||||
|
|
||||||
|
|
21
MiniC/printlib.h
Normal file
21
MiniC/printlib.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
#define print_float(f) do { printf("%.2f", f); } while(0)
|
||||||
|
#define println_float(f) do { printf("%.2f\n", f); } while(0)
|
||||||
|
|
||||||
|
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_sequentialize_moves.py", "replace_target_api.py", "test_replace_mem.py", "MiniCCWrapper.py", "MiniCLexer.py", "MiniCParser.py", "MiniCVisitor.py", "*.diff.py", "**/*.diff.py"]
|
317
MiniC/test_expect_pragma.py
Normal file
317
MiniC/test_expect_pragma.py
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
import collections
|
||||||
|
from pathlib import PurePath
|
||||||
|
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)
|
||||||
|
|
||||||
|
ASM = 'riscv64-unknown-elf-gcc'
|
||||||
|
SIMU = 'spike'
|
||||||
|
|
||||||
|
LIBPRINT = 'libprint.s'
|
||||||
|
if 'LIBPRINT' in os.environ:
|
||||||
|
LIBPRINT = os.environ['LIBPRINT']
|
||||||
|
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
|
def filter_pathnames(pathlist, pattern):
|
||||||
|
return tuple(path for path in pathlist if PurePath(path).match(pattern))
|
||||||
|
|
||||||
|
|
||||||
|
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 skip_if_partial_match(self, actual, expect, ignore_error_message):
|
||||||
|
if not ignore_error_message:
|
||||||
|
return False
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class TestCompiler(object):
|
||||||
|
DISABLE_CODEGEN: bool
|
||||||
|
SKIP_NOT_IMPLEMENTED: bool
|
||||||
|
MINIC_COMPILE: str
|
||||||
|
MINICC_OPTS: list[str]
|
||||||
|
|
||||||
|
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 run_with_gcc(self, file, info):
|
||||||
|
return self.compile_and_simulate(file, info, reg_alloc='gcc', use_gcc=True)
|
||||||
|
|
||||||
|
def compile_with_gcc(self, file, output_name):
|
||||||
|
print("Compiling with GCC...")
|
||||||
|
result = self.run_command(
|
||||||
|
[ASM, '-S', '-I./',
|
||||||
|
'--output=' + output_name,
|
||||||
|
'-Werror',
|
||||||
|
'-Wno-div-by-zero', # We need to accept 1/0 at compile-time
|
||||||
|
file])
|
||||||
|
print(result.output)
|
||||||
|
print("Compiling with GCC... DONE")
|
||||||
|
return result
|
||||||
|
|
||||||
|
def compile_and_simulate(self, file, info, reg_alloc,
|
||||||
|
use_gcc=False):
|
||||||
|
basename, _ = os.path.splitext(file)
|
||||||
|
output_name = basename + '-' + reg_alloc + '.s'
|
||||||
|
if use_gcc:
|
||||||
|
result = self.compile_with_gcc(file, output_name)
|
||||||
|
if result.exitcode != 0:
|
||||||
|
# We don't consider the exact exitcode, and ignore the
|
||||||
|
# output (our error messages may be different from
|
||||||
|
# GCC's)
|
||||||
|
return result._replace(exitcode=1,
|
||||||
|
output=None)
|
||||||
|
else:
|
||||||
|
result = self.compile_with_ours(file, output_name, reg_alloc)
|
||||||
|
if (self.DISABLE_CODEGEN or
|
||||||
|
reg_alloc == 'none' or
|
||||||
|
info.exitcode != 0 or result.exitcode != 0):
|
||||||
|
# Either the result is meaningless, or we already failed
|
||||||
|
# and don't need to go any further:
|
||||||
|
return result
|
||||||
|
# Only executable code past this point.
|
||||||
|
exec_name = basename + '-' + reg_alloc + '.riscv'
|
||||||
|
return self.link_and_run(output_name, exec_name, info)
|
||||||
|
|
||||||
|
def link_and_run(self, output_name, exec_name, info):
|
||||||
|
self.remove(exec_name)
|
||||||
|
cmd = [
|
||||||
|
ASM, output_name, LIBPRINT,
|
||||||
|
'-o', exec_name
|
||||||
|
] + info.linkargs
|
||||||
|
print(info)
|
||||||
|
print("Assembling and linking " + output_name + ": " + ' '.join(cmd))
|
||||||
|
try:
|
||||||
|
subprocess.check_output(cmd, timeout=60, stderr=subprocess.STDOUT)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print("Assembling failed:\n")
|
||||||
|
print(e.output.decode())
|
||||||
|
print("Assembler code below:\n")
|
||||||
|
cat(output_name)
|
||||||
|
pytest.fail()
|
||||||
|
assert (os.path.isfile(exec_name))
|
||||||
|
sys.stdout.write("Assembling and linking ... OK\n")
|
||||||
|
try:
|
||||||
|
result = self.run_command(
|
||||||
|
[SIMU,
|
||||||
|
'-m100', # Limit memory usage to 100MB, more than enough and
|
||||||
|
# avoids crashing on a VM with <= 2GB RAM for example.
|
||||||
|
'pk',
|
||||||
|
exec_name],
|
||||||
|
scope="runtime")
|
||||||
|
output = re.sub(r'bbl loader\r?\n', '', result.output)
|
||||||
|
print("Output of the program:")
|
||||||
|
print(output)
|
||||||
|
return testinfo(execcode=result.execcode,
|
||||||
|
exitcode=result.exitcode,
|
||||||
|
output=output,
|
||||||
|
linkargs=[],
|
||||||
|
skip_test_expected=False)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
pytest.fail("Timeout executing program. Infinite loop in generated code?")
|
||||||
|
|
||||||
|
def compile_with_ours(self, file, output_name, reg_alloc):
|
||||||
|
print("Compiling ...")
|
||||||
|
self.remove(output_name)
|
||||||
|
alloc_opt = '--reg-alloc=' + reg_alloc
|
||||||
|
out_opt = '--output=' + output_name
|
||||||
|
cmd = [sys.executable, self.MINIC_COMPILE]
|
||||||
|
if not self.DISABLE_CODEGEN:
|
||||||
|
cmd += [out_opt, alloc_opt]
|
||||||
|
cmd += self.MINICC_OPTS
|
||||||
|
cmd += [file]
|
||||||
|
result = self.run_command(cmd)
|
||||||
|
print(' '.join(cmd))
|
||||||
|
print("Exited with status:", result.exitcode)
|
||||||
|
print(result.output)
|
||||||
|
if result.exitcode == 4:
|
||||||
|
if "AllocationError" in result.output:
|
||||||
|
if reg_alloc == 'naive':
|
||||||
|
pytest.skip("Too big for the naive allocator")
|
||||||
|
else:
|
||||||
|
pytest.skip("Offsets too big to be manipulated")
|
||||||
|
elif ("NotImplementedError" in result.output and
|
||||||
|
self.SKIP_NOT_IMPLEMENTED):
|
||||||
|
pytest.skip("Feature not implemented in this compiler")
|
||||||
|
if result.exitcode != 0:
|
||||||
|
# May either be a failing test or a test with expected
|
||||||
|
# compilation failure (bad type, ...). Let the caller
|
||||||
|
# do the assertion and decide:
|
||||||
|
return result
|
||||||
|
if not self.DISABLE_CODEGEN:
|
||||||
|
assert os.path.isfile(output_name)
|
||||||
|
print("Compiling ... OK")
|
||||||
|
return result
|
100
MiniC/test_interpreter.py
Executable file
100
MiniC/test_interpreter.py
Executable file
@ -0,0 +1,100 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
import pytest
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from test_expect_pragma import (
|
||||||
|
TestExpectPragmas, cat,
|
||||||
|
TestCompiler, filter_pathnames
|
||||||
|
)
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
if HERE == os.path.realpath('.'):
|
||||||
|
HERE = '.'
|
||||||
|
TEST_DIR = HERE
|
||||||
|
IMPLEM_DIR = HERE
|
||||||
|
|
||||||
|
SKIP_EXPECT = False
|
||||||
|
if 'SKIP_EXPECT' in os.environ:
|
||||||
|
SKIP_EXPECT = True
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
if 'FILTER' in os.environ:
|
||||||
|
ALL_FILES = filter_pathnames(ALL_FILES, os.environ['FILTER'])
|
||||||
|
|
||||||
|
|
||||||
|
class TestInterpret(TestExpectPragmas, TestCompiler):
|
||||||
|
DISABLE_CODEGEN = False
|
||||||
|
|
||||||
|
def evaluate(self, file):
|
||||||
|
if not DISABLE_TYPECHECK:
|
||||||
|
res = self.run_command([sys.executable, MINIC_EVAL,
|
||||||
|
"--mode", "eval", file])
|
||||||
|
else:
|
||||||
|
res = self.run_command([sys.executable, MINIC_EVAL,
|
||||||
|
"--mode", "eval",
|
||||||
|
"--disable-typecheck", file])
|
||||||
|
if res.exitcode == 1:
|
||||||
|
# Execution can't distinguish exit code at runtime and static
|
||||||
|
# rejection of the program. But we know that an exit code of 1 is
|
||||||
|
# reserved for runtime errors, hence convert this exitcode
|
||||||
|
# into an execcode.
|
||||||
|
res = res._replace(exitcode=0, execcode=1)
|
||||||
|
return res
|
||||||
|
|
||||||
|
# Not in test_expect_pragma to get assertion rewritting
|
||||||
|
def assert_equal(self, actual, expected, compiler):
|
||||||
|
if expected.output is not None and actual.output is not None:
|
||||||
|
assert actual.output == expected.output, \
|
||||||
|
f"Output of the program is incorrect (using {compiler})."
|
||||||
|
assert actual.exitcode == expected.exitcode, \
|
||||||
|
f"Exit code of the compiler ({compiler}) is incorrect"
|
||||||
|
assert actual.execcode == expected.execcode, \
|
||||||
|
f"Exit code of the execution is incorrect (using {compiler})"
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('filename', ALL_FILES)
|
||||||
|
def test_expect(self, filename):
|
||||||
|
"""Test the EXPECTED annotations in test files by launching the
|
||||||
|
program with GCC."""
|
||||||
|
if SKIP_EXPECT:
|
||||||
|
pytest.skip("Skipping all test_expect "
|
||||||
|
"because $SKIP_EXPECT is set.")
|
||||||
|
cat(filename) # For diagnosis
|
||||||
|
expect = self.get_expect(filename)
|
||||||
|
if expect.skip_test_expected:
|
||||||
|
pytest.skip("Skipping test_expect with GCC because "
|
||||||
|
"the test contains SKIP TEST EXPECTED")
|
||||||
|
if expect.exitcode != 0:
|
||||||
|
# GCC is more permissive than us, so trying to compile an
|
||||||
|
# incorrect program would bring us no information (it may
|
||||||
|
# compile, or fail with a different message...)
|
||||||
|
pytest.skip("Not testing the expected value for "
|
||||||
|
"tests expecting exitcode!=0")
|
||||||
|
gcc_result = self.run_with_gcc(filename, expect)
|
||||||
|
self.assert_equal(gcc_result, expect, "gcc")
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('filename', ALL_FILES)
|
||||||
|
def test_eval(self, filename):
|
||||||
|
cat(filename) # For diagnosis
|
||||||
|
expect = self.get_expect(filename)
|
||||||
|
actual = self.evaluate(filename)
|
||||||
|
if expect:
|
||||||
|
self.assert_equal(actual, expect, "MiniCC")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pytest.main(sys.argv)
|
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
Executable file
36
TP03/arith-visitor/test_arith_visitor.py
Executable 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…
Reference in New Issue
Block a user