Merge branch 'main' of https://github.com/Drup/cap-lab24.git
This commit is contained in:
commit
c527169d0d
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