diff --git a/TP02/ITE/ITE.g4 b/TP02/ITE/ITE.g4 new file mode 100644 index 0000000..e20849b --- /dev/null +++ b/TP02/ITE/ITE.g4 @@ -0,0 +1,11 @@ +grammar ITE; + +prog: stmt EOF; + +stmt : ifStmt | ID ; + +ifStmt : 'if' ID 'then' thenstmt=stmt ('else' elsestmt=stmt)?; + + +ID : [a-zA-Z]+; +WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines diff --git a/TP02/ITE/Makefile b/TP02/ITE/Makefile new file mode 100644 index 0000000..999ac96 --- /dev/null +++ b/TP02/ITE/Makefile @@ -0,0 +1,17 @@ +MAINFILE = main +PACKAGE = ITE + +ifndef ANTLR4 +$(error variable ANTLR4 is not set) +endif + +default: $(PACKAGE)Parser.py + +$(PACKAGE)Parser.py: $(PACKAGE).g4 + $(ANTLR4) $^ -Dlanguage=Python3 + +run: $(MAINFILE).py $(PACKAGE)Parser.py + python3 $< + +clean: + rm -rf *~ $(PACKAGE)*.py $(PACKAGE)*.pyc *.interp *.tokens __pycache* diff --git a/TP02/ITE/main.py b/TP02/ITE/main.py new file mode 100644 index 0000000..6235eeb --- /dev/null +++ b/TP02/ITE/main.py @@ -0,0 +1,21 @@ +from antlr4 import InputStream +from antlr4 import CommonTokenStream + +# include to use the generated lexer and parser +from ITELexer import ITELexer +from ITEParser import ITEParser + +import sys + + +def main(): + lexer = ITELexer(InputStream(sys.stdin.read())) + stream = CommonTokenStream(lexer) + parser = ITEParser(stream) + parser.prog() + print("Finished") + + +# warns pb if py file is included in others +if __name__ == '__main__': + main() diff --git a/TP02/ariteval/Arit.g4 b/TP02/ariteval/Arit.g4 new file mode 100644 index 0000000..53e1d98 --- /dev/null +++ b/TP02/ariteval/Arit.g4 @@ -0,0 +1,27 @@ +grammar Arit; + +// MIF08@Lyon1 and CAP@ENSL, arit evaluator + +@header { +# header - mettre les déclarations globales +import sys +idTab = {}; + +class UnknownIdentifier(Exception): + pass + +class DivByZero(Exception): + pass + +} + +prog: ID {print("prog = "+str($ID.text));} ; + + +COMMENT + : '//' ~[\r\n]* -> skip + ; + +ID : ('a'..'z'|'A'..'Z')+; +INT: '0'..'9'+; +WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines diff --git a/TP02/ariteval/Makefile b/TP02/ariteval/Makefile new file mode 100644 index 0000000..e905279 --- /dev/null +++ b/TP02/ariteval/Makefile @@ -0,0 +1,36 @@ +MAINFILE = arit +PACKAGE = Arit + +ifndef ANTLR4 +$(error variable ANTLR4 is not set) +endif + +$(PACKAGE)Listener.py $(PACKAGE)Lexer.py $(PACKAGE)Lexer.tokens $(PACKAGE)Parser.py $(PACKAGE).tokens: $(PACKAGE).g4 + $(ANTLR4) $< -Dlanguage=Python3 + +main-deps: $(PACKAGE)Lexer.py $(PACKAGE)Parser.py + +#use pytest !! + +run: $(MAINFILE).py main-deps + python3 $< + +TESTFILE=tests/test01.txt + +print-lisp: $(MAINFILE).py main-deps + python3 $< $(TESTFILE) --lisp + +print-tree: $(MAINFILE).py main-deps + python3 $< $(TESTFILE) --lisp --debug + +test: test_ariteval.py main-deps + python3 -m pytest -v $< + +tar: clean + dir=$$(basename "$$PWD") && cd .. && \ + tar cvfz "$$dir.tgz" --exclude="*.riscv" --exclude=".git" --exclude=".pytest_cache" \ + --exclude="htmlcov" --exclude="*.dot" --exclude="*.pdf" "$$dir" + @echo "Created ../$$dir.tgz" + +clean: + rm -rf *~ $(PACKAGE)*.py $(PACKAGE)*.pyc *.tokens __pycache* .cache *.interp *.java *.class *.dot *.dot.pdf diff --git a/TP02/ariteval/arit.py b/TP02/ariteval/arit.py new file mode 100644 index 0000000..c6884b1 --- /dev/null +++ b/TP02/ariteval/arit.py @@ -0,0 +1,66 @@ +#! /usr/bin/env python3 +""" +Usage: + python3 arit.py +""" +# Main file for MIF08 - Lab03 - 2018, changed in 2022 + +from AritLexer import AritLexer +from AritParser import AritParser, UnknownIdentifier, DivByZero +from antlr4 import FileStream, CommonTokenStream, StdinStream +from antlr4.tree.Trees import Trees +from antlr4.Utils import escapeWhitespace + +import argparse + + +def getNodeText(node, parser): + return escapeWhitespace(Trees.getNodeText(node, recog=parser), + True).replace('\\', '\\\\') + + +def _toDot(t, g, parser): + for c in Trees.getChildren(t): + g.node(str(id(c)), getNodeText(c, parser)) + g.edge(str(id(t)), str(id(c))) + _toDot(c, g, parser) + + +def toDot(t, parser): + from graphviz import Digraph + g = Digraph() + g.node(str(id(t)), getNodeText(t, parser)) + _toDot(t, g, parser) + g.render("tree.dot", view=True) + + +def main(inputname, lisp, debug): + if inputname is None: + lexer = AritLexer(StdinStream()) + else: + lexer = AritLexer(FileStream(inputname)) + stream = CommonTokenStream(lexer) + parser = AritParser(stream) + try: + tree = parser.prog() + if lisp: + print(tree.toStringTree(tree, parser)) + if debug: + toDot(tree, parser) + except UnknownIdentifier as exc: # Parser's exception + print('{} is undefined'.format(exc.args[0])) + exit(1) + except DivByZero: + print('Division by zero') + exit(1) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='AritEval lab') + parser.add_argument('filename', type=str, nargs='?', help='Source file.') + parser.add_argument('--lisp', default=False, action='store_true', + help="Print parse tree in Lisp format") + parser.add_argument('--debug', default=False, action='store_true', + help="Print parse tree graphically") + args = parser.parse_args() + main(args.filename, args.lisp, args.debug) diff --git a/TP02/ariteval/test_ariteval.py b/TP02/ariteval/test_ariteval.py new file mode 100755 index 0000000..ea8b283 --- /dev/null +++ b/TP02/ariteval/test_ariteval.py @@ -0,0 +1,28 @@ +#! /usr/bin/env python3 +import pytest +import glob +import sys +from test_expect_pragma import TestExpectPragmas + +ALL_FILES = glob.glob('./tests/hello*.txt') + +# only test programs of these shapes! +# ALL_FILES = glob.glob('./tests/test*.txt') +# + glob.glob('./tests/bad*.txt') + +EVAL = 'arit.py' + + +class TestEVAL(TestExpectPragmas): + def evaluate(self, file): + return self.run_command(['python3', EVAL, file]) + + @pytest.mark.parametrize('filename', ALL_FILES) + def test_expect(self, filename): + expect = self.get_expect(filename) + eval = self.evaluate(filename) + assert expect == eval + + +if __name__ == '__main__': + pytest.main(sys.argv) diff --git a/TP02/ariteval/test_expect_pragma.py b/TP02/ariteval/test_expect_pragma.py new file mode 100644 index 0000000..ec869ba --- /dev/null +++ b/TP02/ariteval/test_expect_pragma.py @@ -0,0 +1,95 @@ +import collections +import re +import os +import subprocess +import sys + +testresult = collections.namedtuple('testresult', ['exitcode', 'output']) + + +def cat(filename): + with open(filename, "rb") as f: + for line in f: + sys.stdout.buffer.write(line) + + +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 " where + is an integer, and is the expected exitcode of the command. + + The result is cached to avoid re-parsing the file multiple + times. + """ + if filename not in self.__expect: + self.__expect[filename] = self._extract_expect(filename) + return self.__expect[filename] + + def remove(self, file): + """Like os.remove(), but ignore errors, e.g. don't complain if the + file doesn't exist. + """ + try: + os.remove(file) + except OSError: + pass + + def run_command(self, cmd): + """Run the command cmd (given as [command, arg1, arg2, ...]), and + return testresult(exitcode=..., output=...) containing the + exit code of the command it its standard output + standard error. + """ + try: + output = subprocess.check_output(cmd, timeout=60, + stderr=subprocess.STDOUT) + exitcode = 0 + except subprocess.CalledProcessError as e: + output = e.output + exitcode = e.returncode + return testresult(exitcode=exitcode, output=output.decode()) + + __expect = {} + + def _extract_expect(self, file): + exitcode = 0 + inside_expected = False + expected_lines = [] + 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 == 'EXPECTED': + inside_expected = True + elif inside_expected: + expected_lines.append(line) + + expected_lines.append('') + return testresult(exitcode=exitcode, + output=os.linesep.join(expected_lines)) diff --git a/TP02/ariteval/tests/bad01.txt b/TP02/ariteval/tests/bad01.txt new file mode 100644 index 0000000..b108673 --- /dev/null +++ b/TP02/ariteval/tests/bad01.txt @@ -0,0 +1,4 @@ +a = 4 +b ; +// EXPECTED +// b is undefined +// EXITCODE 1 diff --git a/TP02/ariteval/tests/hello01.txt b/TP02/ariteval/tests/hello01.txt new file mode 100644 index 0000000..9392527 --- /dev/null +++ b/TP02/ariteval/tests/hello01.txt @@ -0,0 +1,3 @@ +Hello +// EXPECTED +// prog = Hello diff --git a/TP02/ariteval/tests/test01.txt b/TP02/ariteval/tests/test01.txt new file mode 100644 index 0000000..a6e2e67 --- /dev/null +++ b/TP02/ariteval/tests/test01.txt @@ -0,0 +1,10 @@ +20 + 22; +a = 4; +a + 2; +a * 5; + +// EXPECTED +// 20+22 = 42 +// a now equals 4 +// a+2 = 6 +// a*5 = 20 diff --git a/TP02/demo_files/ex1/.gitignore b/TP02/demo_files/ex1/.gitignore new file mode 100644 index 0000000..28a2742 --- /dev/null +++ b/TP02/demo_files/ex1/.gitignore @@ -0,0 +1 @@ +/Example1.py diff --git a/TP02/demo_files/ex1/Example1.g4 b/TP02/demo_files/ex1/Example1.g4 new file mode 100644 index 0000000..0f526a4 --- /dev/null +++ b/TP02/demo_files/ex1/Example1.g4 @@ -0,0 +1,9 @@ +//define a lexical analyser called Example1 + +lexer grammar Example1; + +OP : '+'| '*' | '-' | '/' ; +DIGIT : [0-9] ; +LETTER : [A-Za-z] ; +ID : LETTER (LETTER | DIGIT)* ; // match idents +WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines diff --git a/TP02/demo_files/ex1/Makefile b/TP02/demo_files/ex1/Makefile new file mode 100644 index 0000000..8d3858f --- /dev/null +++ b/TP02/demo_files/ex1/Makefile @@ -0,0 +1,17 @@ +MAINFILE = main +PACKAGE = Example1 + +ifndef ANTLR4 +$(error variable ANTLR4 is not set) +endif + +default: $(PACKAGE).py + +$(PACKAGE).py: $(PACKAGE).g4 + $(ANTLR4) $^ -Dlanguage=Python3 + +run: $(MAINFILE).py $(PACKAGE)*.py + python3 $< + +clean: + rm -rf *~ $(PACKAGE)*.py $(PACKAGE)*.pyc *.interp *.tokens __pycache* diff --git a/TP02/demo_files/ex1/main.py b/TP02/demo_files/ex1/main.py new file mode 100644 index 0000000..d05d56e --- /dev/null +++ b/TP02/demo_files/ex1/main.py @@ -0,0 +1,25 @@ +from antlr4 import InputStream +from antlr4 import CommonTokenStream +from Example1 import Example1 + +import sys + + +def main(): + # InputStream reads characters (from stdin in our case) + input_stream = InputStream(sys.stdin.read()) + # The generated lexer groups characters into Tokens ... + lexer = Example1(input_stream) + # ... and the stream of Tokens is managed by the TokenStream. + stream = CommonTokenStream(lexer) + + # Display the token stream + stream.fill() # needed to get stream.tokens (otherwise lazily filled-in) + for t in stream.tokens: + print(t) + print("Finished") + + +# warns pb if py file is included in others +if __name__ == '__main__': + main() diff --git a/TP02/demo_files/ex2/Example2.g4 b/TP02/demo_files/ex2/Example2.g4 new file mode 100644 index 0000000..2bfe93f --- /dev/null +++ b/TP02/demo_files/ex2/Example2.g4 @@ -0,0 +1,17 @@ +//define a tiny grammar for arith expressions with identifiers + +grammar Example2; + +full_expr: expr ';' EOF ; + +expr: expr OP expr + | ID {print('oh an id : '+$ID.text)} + | INT + ; + +OP : '+'| '*' | '-' | '/' ; + + +INT : '0'..'9'+ ; +ID : ('a'..'z'|'A'..'Z')+ ; +WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines diff --git a/TP02/demo_files/ex2/Makefile b/TP02/demo_files/ex2/Makefile new file mode 100644 index 0000000..25ace79 --- /dev/null +++ b/TP02/demo_files/ex2/Makefile @@ -0,0 +1,17 @@ +MAINFILE = main +PACKAGE = Example2 + +ifndef ANTLR4 +$(error variable ANTLR4 is not set) +endif + +default: $(PACKAGE)Parser.py + +$(PACKAGE)Parser.py: $(PACKAGE).g4 + $(ANTLR4) $^ -Dlanguage=Python3 + +run: $(MAINFILE).py $(PACKAGE)Parser.py + python3 $< + +clean: + rm -rf *~ $(PACKAGE)*.py $(PACKAGE)*.pyc *.interp *.tokens __pycache* diff --git a/TP02/demo_files/ex2/main.py b/TP02/demo_files/ex2/main.py new file mode 100644 index 0000000..ea4a0c9 --- /dev/null +++ b/TP02/demo_files/ex2/main.py @@ -0,0 +1,22 @@ +from antlr4 import InputStream +from antlr4 import CommonTokenStream + +# include to use the generated lexer and parser +from Example2Lexer import Example2Lexer +from Example2Parser import Example2Parser + +import sys + + +def main(): + input_stream = InputStream(sys.stdin.read()) + lexer = Example2Lexer(input_stream) + stream = CommonTokenStream(lexer) + parser = Example2Parser(stream) + parser.full_expr() # We want to recognize full_expr in grammar Example2 + print("Finished") + + +# warns pb if py file is included in others +if __name__ == '__main__': + main() diff --git a/TP02/demo_files/ex3/Example3.g4 b/TP02/demo_files/ex3/Example3.g4 new file mode 100644 index 0000000..dccd2a9 --- /dev/null +++ b/TP02/demo_files/ex3/Example3.g4 @@ -0,0 +1,18 @@ +//define a tiny grammar with attributes for arith expressions with identifiers + +grammar Example3; + +full_expr: e0=expr ';' EOF {print($e0.text + " has " + str($e0.count) + " operators!")} ; + +expr returns [int count]: // expr has an integer attribute called count + | e0=expr OP e1=expr {$count = $e0.count + $e1.count + 1} // name sub-parts and access their attributes + | ID {$count = 0} + | INT {$count = 0} + ; + +OP : '+'| '*' | '-' | '/' ; + + +INT : '0'..'9'+ ; +ID : ('a'..'z'|'A'..'Z')+ ; +WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines diff --git a/TP02/demo_files/ex3/Makefile b/TP02/demo_files/ex3/Makefile new file mode 100644 index 0000000..7a25a56 --- /dev/null +++ b/TP02/demo_files/ex3/Makefile @@ -0,0 +1,17 @@ +MAINFILE = main +PACKAGE = Example3 + +ifndef ANTLR4 +$(error variable ANTLR4 is not set) +endif + +default: $(PACKAGE)Parser.py + +$(PACKAGE)Parser.py: $(PACKAGE).g4 + $(ANTLR4) $^ -Dlanguage=Python3 + +run: $(MAINFILE).py $(PACKAGE)Parser.py + python3 $< + +clean: + rm -rf *~ $(PACKAGE)*.py $(PACKAGE)*.pyc *.interp *.tokens __pycache* diff --git a/TP02/demo_files/ex3/main.py b/TP02/demo_files/ex3/main.py new file mode 100644 index 0000000..aa2f51c --- /dev/null +++ b/TP02/demo_files/ex3/main.py @@ -0,0 +1,22 @@ +from antlr4 import InputStream +from antlr4 import CommonTokenStream + +# include to use the generated lexer and parser +from Example3Lexer import Example3Lexer +from Example3Parser import Example3Parser + +import sys + + +def main(): + input_stream = InputStream(sys.stdin.read()) + lexer = Example3Lexer(input_stream) + stream = CommonTokenStream(lexer) + parser = Example3Parser(stream) + parser.full_expr() + print("Finished") + + +# warns pb if py file is included in others +if __name__ == '__main__': + main() diff --git a/TP02/python/objects.py b/TP02/python/objects.py new file mode 100644 index 0000000..db07c07 --- /dev/null +++ b/TP02/python/objects.py @@ -0,0 +1,26 @@ +# Class without base class +class Base(): + # Constructor (called by "Base()" when creating an object) + def __init__(self): + self.a = "set in Base's constructor" + + def f(self): + return self.a + + def g(self): + return "returned from Base's g()" + + +class Derived(Base): + # Function with the same name override the base class function. + def f(self): + return "returned from Derived's f()" + + +b = Base() # Create an object of Base +print(b.f()) # Method call, as usual OO languages +print(b.g()) + +d = Derived() +print(d.f()) +print(d.g()) diff --git a/TP02/python/type_unions.py b/TP02/python/type_unions.py new file mode 100644 index 0000000..be5bd60 --- /dev/null +++ b/TP02/python/type_unions.py @@ -0,0 +1,53 @@ +from typing import List + +# int | float means ``either an int or a float''. +NUMBER = int | float # or Union[int, float] with Union imported from typing + + +def add_numbers(a: NUMBER, b: NUMBER) -> NUMBER: + return a + b + + +# Both int and floats can be passed to the function +print(add_numbers(1, 4.3)) + + +def divide_numbers(a: NUMBER, b: NUMBER) -> float: + return a / b + + +print(divide_numbers(1, 2)) + +# Declare the type of a list whose elements are numbers. +LIST_OF_NUMBERS = List[NUMBER] + + +def increment(a: LIST_OF_NUMBERS) -> LIST_OF_NUMBERS: + return [x + 1 for x in a] + + +print(increment([1, 2, 3])) + +# Skip the end if you are late. + +# The type DEEP_LIST_OF_NUMBERS is a special case since it references itself. +# The identifier DEEP_LIST_OF_NUMBERS cannot be used before the end of its +# initialization, but the circular dependency can be broken using the string +# 'DEEP_LIST_OF_NUMBERS' instead. +DEEP_LIST_OF_NUMBERS = NUMBER | List['DEEP_LIST_OF_NUMBERS'] + + +def deep_increment(d: DEEP_LIST_OF_NUMBERS) -> DEEP_LIST_OF_NUMBERS: + if isinstance(d, list): + # Note the unusual typing rule applied by Pyright here: because we are + # in the 'isinstance(d, list)' branch, it knows that d is a list, + # and accepts to iterate over it. + return [deep_increment(e) for e in d] + else: + # ... and here, in the 'else' branch Pyright knows that d is + # not a list, + # and can deduce that it is a NUMBER. + return d + 1 + + +print(deep_increment([1, [2, 3]])) diff --git a/TP02/python/typecheck.py b/TP02/python/typecheck.py new file mode 100644 index 0000000..58f8288 --- /dev/null +++ b/TP02/python/typecheck.py @@ -0,0 +1,16 @@ +# Typing annotations for variables: +# name: type +int_variable: int +float_variable: float +int_variable = 4.2 # Static typing error, but no runtime error +float_variable = 42.0 # OK +float_variable = int_variable # OK + + +# Typing annotations for functions (-> means "returns") +def int_to_string(i: int) -> str: + return str(i) + + +print(int_to_string('Hello')) # Static typing error, but no runtime error +print(int_to_string(42) / 5) # Both static and runtime error diff --git a/TP02/tp2.pdf b/TP02/tp2.pdf new file mode 100644 index 0000000..8eb27e8 Binary files /dev/null and b/TP02/tp2.pdf differ