actually commit TP2
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,35 @@
|
||||
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"
|
||||
|
||||
clean:
|
||||
rm -rf *~ $(PACKAGE)*.py $(PACKAGE)*.pyc *.tokens __pycache* .cache *.interp *.java *.class *.dot *.dot.pdf
|
||||
@@ -0,0 +1,65 @@
|
||||
#! /usr/bin/env python3
|
||||
"""
|
||||
Usage:
|
||||
python3 arit.py <filename>
|
||||
"""
|
||||
# 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)
|
||||
@@ -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)
|
||||
@@ -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 <n>" where <n>
|
||||
is an integer, and is the expected exitcode of the command.
|
||||
|
||||
The result is cached to avoid re-parsing the file multiple
|
||||
times.
|
||||
"""
|
||||
if filename not in self.__expect:
|
||||
self.__expect[filename] = self._extract_expect(filename)
|
||||
return self.__expect[filename]
|
||||
|
||||
def remove(self, file):
|
||||
"""Like os.remove(), but ignore errors, e.g. don't complain if the
|
||||
file doesn't exist.
|
||||
"""
|
||||
try:
|
||||
os.remove(file)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def run_command(self, cmd):
|
||||
"""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))
|
||||
@@ -0,0 +1,4 @@
|
||||
a = 4 +b ;
|
||||
// EXPECTED
|
||||
// b is undefined
|
||||
// EXITCODE 1
|
||||
@@ -0,0 +1,3 @@
|
||||
Hello
|
||||
// EXPECTED
|
||||
// prog = Hello
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user