Commit b2034f65 authored by Matthieu Moy's avatar Matthieu Moy
Browse files

TP02 files

parent f481072e
grammar Arit;
// MIF08, simple arit evaluator with semantic actions
// Reminder: lower-case for parser rules, UPPER-CASE for lexer rules.
prog: statement+ EOF
;
statement: expr SCOL {print($expr.text+" = "+str($expr.val))} // print the value
;
expr returns [int val]
: e1=expr MULT e2=expr {$val = $e1.val*$e2.val} // MULT is * (matched before PLUS if possible)
| e1=expr PLUS e2=expr {$val = $e1.val + $e2.val} // PLUS is +
| a=atom {$val = $a.val} // just copy the value
;
atom returns [int val]
: INT {$val = int($INT.text)} // get the value from the lexer
| '(' expr ')' {$val=$expr.val} // just copy the value
;
SCOL : ';';
PLUS : '+';
MINUS : '-';
MULT : '*';
DIV : '/';
INT: [0-9]+;
COMMENT
: '//' ~[\r\n]* -> skip
;
NEWLINE: '\r'? '\n' -> skip;
WS : (' '|'\t')+ -> skip;
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/foo01.txt
tests: test_ariteval.py main-deps
python3 -m pytest -v $<
clean:
rm -rf *~ $(PACKAGE)*.py $(PACKAGE)*.pyc *.tokens __pycache* .cache *.interp .antlr/
tar: clean
dir=$$(basename "$$PWD") && cd .. && \
tar cvfz "$$dir.tgz" --exclude="*.riscv" --exclude=".git" --exclude=".pytest_cache" \
--exclude="htmlcov" "$$dir"
# LAB2, arithmetic expressions interpreter
MIF08, 2020-2021, Laure Gonnord & Matthieu Moy
# Content
This directory contains an interpreter for simple arithmetic
expressions like 5+3, for instance. The intepreter evaluates the
arithmetic expressions and prints their value on the standard
output.
# Usage
* `make` to generate AritLexer.py and AritParser.py (once)
* `python3 arit1.py <path/and/test/name>` to test a given file, for
instance:
`python3 arit1.py tests/test01.txt` should print `1+2 = 3`
* `make tests` to test on all tests files of the `testfile` directory
# Syntax of our language/restrictions
The syntax is the one textually given in the Lab2 sheet.
Restriction : we did not implement minus nor unary minus.
# Design choices
TODO
# Known bugs
N/A
#! /usr/bin/env python3
"""
Usage:
python3 arit1.py <filename>
"""
# Main file for MIF08 - Lab02 - 2018-19
from AritLexer import AritLexer
from AritParser import AritParser
from antlr4 import FileStream, CommonTokenStream, StdinStream
# example of arithmetic eval with semantic actions
import argparse
def main(inputname):
if inputname is None:
lexer = AritLexer(StdinStream())
else:
lexer = AritLexer(FileStream(inputname))
stream = CommonTokenStream(lexer)
parser = AritParser(stream)
parser.prog()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='AritEval demo')
parser.add_argument('filename', type=str, nargs='?',
help='Source file.')
args = parser.parse_args()
main(args.filename)
#! /usr/bin/env python3
import pytest
import glob
import sys
from test_expect_pragma import TestExpectPragmas
ALL_FILES = glob.glob('./tests/test*.txt')
ARIT_EVAL = 'arit.py'
class TestEVAL(TestExpectPragmas):
def evaluate(self, file):
return self.run_command(['python3', ARIT_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)
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='\n'.join(expected_lines))
1+2;
//EXPECTED
//1+2 = 3
1+3*2;
(1+3)*2;
//EXPECTED
//1+3*2 = 7
//(1+3)*2 = 8
//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
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*
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()
//define a tiny grammar for arith expressions with identifiers
grammar Example2;
r: 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
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*
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.r()
print("Finished")
# warns pb if py file is included in others
if __name__ == '__main__':
main()
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment