tp futures
This commit is contained in:
parent
587924218a
commit
f164364595
15
TPfutures/MiniC-futures/.gitignore
vendored
Normal file
15
TPfutures/MiniC-futures/.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
/MiniCLexer.py
|
||||
/MiniCParser.py
|
||||
/MiniCVisitor.py
|
||||
/MiniCListener.py
|
||||
*.dot
|
||||
*.dot.pdf
|
||||
*.riscv
|
||||
*-naive.s
|
||||
*-gcc.s
|
||||
*-all_in_mem.s
|
||||
*-smart.s
|
||||
*.cfut
|
||||
*.o
|
||||
/MiniC.interp
|
||||
/MiniCLexer.interp
|
14
TPfutures/MiniC-futures/Errors.py
Normal file
14
TPfutures/MiniC-futures/Errors.py
Normal file
@ -0,0 +1,14 @@
|
||||
class MiniCRuntimeError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MiniCInternalError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MiniCUnsupportedError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MiniCTypeError(Exception):
|
||||
pass
|
56
TPfutures/MiniC-futures/Makefile
Normal file
56
TPfutures/MiniC-futures/Makefile
Normal file
@ -0,0 +1,56 @@
|
||||
PACKAGE = MiniC
|
||||
# Example: stop at the first failed test:
|
||||
# make PYTEST_OPTS=-x tests
|
||||
PYTEST_OPTS =
|
||||
# Run the whole test infrastructure for a subset of test files e.g.
|
||||
# make TEST_FILES='TP03/**/bad*.c' tests
|
||||
ifdef TEST_FILES
|
||||
export TEST_FILES
|
||||
endif
|
||||
|
||||
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
|
||||
|
||||
main-deps: MiniCLexer.py MiniCParser.py MiniCTypingVisitor.py MiniCPPListener.py
|
||||
|
||||
%.o: %.c $(HEADERS)
|
||||
gcc -g -c -Wall -Wextra $< -o $@
|
||||
|
||||
$(TESTFILE:%.c=%.o): $(TESTFILE:%.c=%.cfut) $(HEADERS)
|
||||
gcc -g -c -Iinclude -Wall -Wextra -x c $(TESTFILE:%.c=%.cfut) -o $(TESTFILE:%.c=%.o)
|
||||
|
||||
$(TESTFILE:%.c=%.out): $(TESTFILE:%.c=%.o) lib/futurelib.o
|
||||
gcc -g $(TESTFILE:%.c=%.o) lib/futurelib.o -o $(TESTFILE:%.c=%.out) -lpthread
|
||||
|
||||
$(TESTFILE:%.c=%.cfut): main-deps
|
||||
python3 MiniCC.py $(TESTFILE)
|
||||
|
||||
run: $(TESTFILE:%.c=%.out)
|
||||
$(TESTFILE:%.c=%.out)
|
||||
|
||||
test: test_futures.py main-deps
|
||||
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_futures.py
|
||||
|
||||
tar: clean
|
||||
dir=$$(basename "$$PWD") && cd .. && \
|
||||
tar cvfz "$$dir.tgz" --exclude="*.riscv" --exclude=".git" --exclude=".pytest_cache" \
|
||||
--exclude="htmlcov" "$$dir"
|
||||
@echo "Created ../$$(basename "$$PWD").tgz"
|
||||
|
||||
clean-tests:
|
||||
cd tests && \
|
||||
find . \( -iname "*.cfut" -or -iname "*.out" \) -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" -or -iname "*.o" \) -print0 | xargs -0 rm -rf \;
|
||||
rm -rf *~ $(PACKAGE)Parser.py $(PACKAGE)Lexer.py $(PACKAGE)Visitor.py $(PACKAGE)Listener.py .coverage .benchmarks
|
164
TPfutures/MiniC-futures/MiniC.g4
Normal file
164
TPfutures/MiniC-futures/MiniC.g4
Normal file
@ -0,0 +1,164 @@
|
||||
grammar MiniC;
|
||||
|
||||
prog: include* function* EOF #progRule;
|
||||
|
||||
//include statements reduced to string
|
||||
include: INCLUDE STRING #includestat ;
|
||||
|
||||
function
|
||||
: typee ID OPAR param_l? CPAR OBRACE vardecl_l block RETURN expr SCOL CBRACE #funcDef
|
||||
| typee ID OPAR param_l? CPAR SCOL #funcDecl
|
||||
;
|
||||
|
||||
vardecl_l: vardecl* #varDeclList;
|
||||
|
||||
vardecl: typee id_l SCOL #varDecl;
|
||||
|
||||
param: typee ID #paramDecl;
|
||||
|
||||
param_l
|
||||
: param #paramListBase
|
||||
| param COM param_l #paramList
|
||||
;
|
||||
|
||||
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
|
||||
: PRINTINT OPAR expr CPAR SCOL #printlnintStat
|
||||
| PRINTFLOAT OPAR expr CPAR SCOL #printlnfloatStat
|
||||
| PRINTBOOL OPAR expr CPAR SCOL #printlnboolStat
|
||||
| PRINTSTRING OPAR expr CPAR SCOL #printstringStat
|
||||
;
|
||||
|
||||
expr_l
|
||||
: expr #exprListBase
|
||||
| expr COM expr_l #exprList
|
||||
;
|
||||
|
||||
|
||||
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
|
||||
| ID OPAR expr_l? CPAR #funcCall
|
||||
| GET OPAR expr CPAR #getCall
|
||||
| ASYNC OPAR ID COM expr_l? CPAR #asyncFuncCall
|
||||
| atom #atomExpr
|
||||
;
|
||||
|
||||
atom
|
||||
: OPAR expr CPAR #parExpr
|
||||
| INT #intAtom
|
||||
| FLOAT #floatAtom
|
||||
| (TRUE | FALSE) #booleanAtom
|
||||
| ID #idAtom
|
||||
| STRING #stringAtom
|
||||
;
|
||||
|
||||
typee
|
||||
: mytype=(INTTYPE|FLOATTYPE|BOOLTYPE|STRINGTYPE|FUTINTTYPE) #basicType
|
||||
;
|
||||
|
||||
|
||||
ASYNC : 'Async';
|
||||
GET : 'Get';
|
||||
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';
|
||||
PRINTINT : 'println_int';
|
||||
PRINTBOOL : 'println_bool';
|
||||
PRINTSTRING : 'println_string';
|
||||
PRINTFLOAT : 'println_float';
|
||||
|
||||
FUTINTTYPE: 'futint';
|
||||
INTTYPE: 'int';
|
||||
FLOATTYPE: 'float';
|
||||
STRINGTYPE: 'string';
|
||||
BOOLTYPE : 'bool';
|
||||
INCLUDE : '#include';
|
||||
|
||||
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
|
||||
// BUT now we want to keep includes => # removed
|
||||
: '//' ~[\r\n]* -> skip
|
||||
;
|
||||
|
||||
SPACE
|
||||
// here is a little for code rewriting.
|
||||
: [ \t\r\n] -> channel(HIDDEN)
|
||||
;
|
||||
|
112
TPfutures/MiniC-futures/MiniCC.py
Normal file
112
TPfutures/MiniC-futures/MiniCC.py
Normal file
@ -0,0 +1,112 @@
|
||||
#! /usr/bin/env python3
|
||||
"""
|
||||
MiniC-futures Lab. Language Extension for MiniC with future primitives
|
||||
Usage:
|
||||
python3 Main.py <filename>
|
||||
python3 Main.py --help
|
||||
"""
|
||||
import traceback
|
||||
from MiniCLexer import MiniCLexer
|
||||
from MiniCParser import MiniCParser
|
||||
from MiniCTypingVisitor import MiniCTypingVisitor, MiniCTypeError
|
||||
from MiniCPPListener import MiniCPPListener
|
||||
from Errors import MiniCUnsupportedError, MiniCInternalError
|
||||
|
||||
import argparse
|
||||
|
||||
from antlr4 import FileStream, CommonTokenStream, ParseTreeWalker
|
||||
from antlr4.error.ErrorListener import ErrorListener
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
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 occured
|
||||
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,
|
||||
typecheck=True, typecheck_only=False, stdout=False, output_name=None, debug=False):
|
||||
(basename, rest) = os.path.splitext(inputname)
|
||||
if not typecheck_only:
|
||||
if stdout:
|
||||
output_name = None
|
||||
elif output_name is None:
|
||||
output_name = basename + ".cfut"
|
||||
|
||||
input_s = FileStream(inputname, encoding='utf-8')
|
||||
lexer = MiniCLexer(input_s)
|
||||
counter = CountErrorListener()
|
||||
lexer._listeners.append(counter)
|
||||
stream = 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 typecheck_only:
|
||||
if debug:
|
||||
print("Not running code generation because of --disable-codegen.")
|
||||
return
|
||||
|
||||
pw = ParseTreeWalker()
|
||||
extractor = MiniCPPListener(stream)
|
||||
pw.walk(extractor, tree)
|
||||
with open(output_name, 'w') if output_name else sys.stdout as output:
|
||||
extractor.printrw(output)
|
||||
|
||||
|
||||
# command line management
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='Generate code for .c file')
|
||||
|
||||
parser.add_argument('filename', type=str,
|
||||
help='Source file.')
|
||||
parser.add_argument('--stdout', action='store_true',
|
||||
help='Generate code to stdout')
|
||||
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 generating code")
|
||||
parser.add_argument('--disable-codegen', action='store_true',
|
||||
default=False,
|
||||
help="Run only the typechecker, don't try generating code.")
|
||||
parser.add_argument('--output', type=str,
|
||||
help='Generate code to outfile')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
main(args.filename,
|
||||
not args.disable_typecheck, args.disable_codegen,
|
||||
args.stdout, args.output, args.debug,
|
||||
)
|
||||
except MiniCUnsupportedError as e:
|
||||
print(e)
|
||||
exit(5)
|
||||
except (MiniCInternalError):
|
||||
traceback.print_exc()
|
||||
exit(4)
|
27
TPfutures/MiniC-futures/MiniCPPListener.py
Normal file
27
TPfutures/MiniC-futures/MiniCPPListener.py
Normal file
@ -0,0 +1,27 @@
|
||||
from MiniCListener import MiniCListener
|
||||
|
||||
from antlr4.TokenStreamRewriter import TokenStreamRewriter
|
||||
|
||||
|
||||
class MiniCPPListener(MiniCListener):
|
||||
def __init__(self, t):
|
||||
self.rewriter = TokenStreamRewriter(tokens=t)
|
||||
|
||||
def printrw(self, output):
|
||||
output.write(self.rewriter.getText('default', 0, 1000))
|
||||
|
||||
def enterProgRule(self, ctx):
|
||||
"""Adds an include futurelib.h at the beginning of program."""
|
||||
indexprog = ctx.start.tokenIndex
|
||||
self.rewriter.insertBeforeIndex(indexprog, '#include \"futurelib.h\"\n')
|
||||
|
||||
def exitFuncDef(self, ctx):
|
||||
"""Adds a call to freeAllFutures at the end of the body of the main function."""
|
||||
(indexret, endret) = ctx.RETURN().getSourceInterval()
|
||||
if ctx.ID().getText() == "main":
|
||||
self.rewriter.insertBeforeIndex(indexret, 'freeAllFutures();\n')
|
||||
|
||||
def enterAsyncFuncCall(self, ctx):
|
||||
"""Adds a & for getting a function pointer to the asynchronous called function."""
|
||||
indexfunid = ctx.start.tokenIndex # token of async
|
||||
self.rewriter.insertBeforeIndex(indexfunid + 2, '&')
|
17
TPfutures/MiniC-futures/MiniCTypingVisitor.py
Normal file
17
TPfutures/MiniC-futures/MiniCTypingVisitor.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Visitor to *typecheck* MiniC files
|
||||
from typing import List
|
||||
from MiniCVisitor import MiniCVisitor
|
||||
from MiniCParser import MiniCParser
|
||||
from Lib.Errors import MiniCInternalError, MiniCTypeError
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
# NEW: ADD FutInteger
|
||||
class BaseType(Enum):
|
||||
Float, Integer, Boolean, String, FutInteger = range(5)
|
||||
|
||||
|
||||
class MiniCTypingVisitor(MiniCVisitor):
|
||||
# TODO Add your own typer here
|
||||
pass
|
25
TPfutures/MiniC-futures/README.md
Normal file
25
TPfutures/MiniC-futures/README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# MiniC Compiler
|
||||
LAB8 (Futures) CAP 2022-23
|
||||
|
||||
# Authors
|
||||
|
||||
YOUR NAME HERE
|
||||
|
||||
# Contents
|
||||
|
||||
TODO:
|
||||
- Explain any design choices you may have made.
|
||||
- Did you implement a bonus?
|
||||
|
||||
# Howto
|
||||
|
||||
`make run TESTFILE=tests/provided/test_fut0.c`: launch the compiler, then GCC and run a single file.
|
||||
|
||||
# Test design
|
||||
|
||||
TODO: explain your tests.
|
||||
|
||||
# Known bugs
|
||||
|
||||
TODO: bugs you could not fix (if any) and limitations.
|
||||
|
12
TPfutures/MiniC-futures/include/compat.h
Normal file
12
TPfutures/MiniC-futures/include/compat.h
Normal file
@ -0,0 +1,12 @@
|
||||
#include "stdio.h"
|
||||
|
||||
typedef char * string;
|
||||
typedef int bool;
|
||||
static const int true = 1;
|
||||
static const int false = 0;
|
||||
|
||||
void print_int(int i) {printf("%i",i);}
|
||||
void println_int(int i) {printf("%i\n",i);}
|
||||
|
||||
void print_string(string);
|
||||
void println_string(string);
|
34
TPfutures/MiniC-futures/include/futurelib.h
Normal file
34
TPfutures/MiniC-futures/include/futurelib.h
Normal file
@ -0,0 +1,34 @@
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct // a struct for storing a future to int
|
||||
{
|
||||
int Id;
|
||||
int Value;
|
||||
int resolved;
|
||||
pthread_t tid;
|
||||
} FutureInt;
|
||||
|
||||
typedef FutureInt* futint; // a typedef to deal with the future type of MiniCFut
|
||||
|
||||
FutureInt *fresh_future_malloc(); // allocates (malloc) a fresh future and initializes its field
|
||||
|
||||
void print_futureInt(FutureInt *fut); // for debug purposes: print a fut int status
|
||||
|
||||
void free_future(FutureInt *fut); // frees the pointer allocated by fresh_future
|
||||
|
||||
void resolve_future(FutureInt *fut, int val); // function called when an async call is finished
|
||||
|
||||
int Get(FutureInt *fut);
|
||||
// called by the main program:
|
||||
// checks that the future is resolved and
|
||||
// returns the value stored in the future
|
||||
|
||||
FutureInt *Async(int (*fun)(int), int p);
|
||||
// asynchronous function call:
|
||||
// takes a function pointer as parameter and the fun call parameter as second parameter
|
||||
// returns an unresolved future
|
||||
// creates a thread to perform the asynchronous function call
|
||||
|
||||
void freeAllFutures();
|
||||
// called at the end of the main block: waits for the resolution of all futures
|
||||
// and frees all future pointers created by fresh_future
|
77
TPfutures/MiniC-futures/lib/futurelib.c
Normal file
77
TPfutures/MiniC-futures/lib/futurelib.c
Normal file
@ -0,0 +1,77 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include "../include/futurelib.h"
|
||||
|
||||
FutureInt *All[70];
|
||||
// A table of future pointers to store all the futures created
|
||||
// In a realistic scenario this would be a dynamic structure
|
||||
int NbAll = 0;
|
||||
// Number of futures created
|
||||
|
||||
typedef struct
|
||||
// A structure for the argument of thread creation: function pointer and parameter
|
||||
{
|
||||
FutureInt *fut;
|
||||
int (*fun)(int);
|
||||
int param;
|
||||
} arg_struct;
|
||||
|
||||
FutureInt *fresh_future_malloc()
|
||||
{
|
||||
// TODO Exercise 4.2
|
||||
// Use malloc(sizeof(FutureInt)) and reference created futures
|
||||
}
|
||||
|
||||
void print_futureInt(FutureInt *f)
|
||||
{
|
||||
// TODO
|
||||
// For debug purposes only
|
||||
}
|
||||
|
||||
void free_future(FutureInt *fut)
|
||||
{
|
||||
free(fut);
|
||||
}
|
||||
|
||||
void resolve_future(FutureInt *fut, int val)
|
||||
{
|
||||
// TODO Exercise 5.1
|
||||
// Fill fut accordingly
|
||||
}
|
||||
|
||||
int Get(FutureInt *fut)
|
||||
{
|
||||
// TODO Exercise 5.2
|
||||
// Wait until future is resolved (do a sleep(1) between two checks)
|
||||
// Do not forget to do a pthread_join(fut->tid, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *runTask(void *param)
|
||||
{
|
||||
// TODO Exercise 4.1
|
||||
// function that is launched by the created thread: should call the function and
|
||||
// deal with the future, using the function resolve_future
|
||||
// param can be cast to (arg_struct *)
|
||||
// this function should free the pointer param
|
||||
return NULL;
|
||||
}
|
||||
|
||||
FutureInt *Async(int (*fun)(int), int p)
|
||||
{
|
||||
// TODO Exercise 4.3
|
||||
// Main system call should be: int err = pthread_create(&fut->tid, NULL, &runTask, (args));
|
||||
// Allocate a future and space for arguments: args = malloc(sizeof(arg_struct));
|
||||
// Do not forget to populate args
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void freeAllFutures()
|
||||
{
|
||||
// TODO Exercises 4.4 & 5.3
|
||||
// 1 - Wait for all futures (Get) to avoid dangling threads (Exercise 5.3)
|
||||
// 2 - Call free_future for all futures (Exercise 4.4)
|
||||
}
|
193
TPfutures/MiniC-futures/test_expect_pragma.py
Normal file
193
TPfutures/MiniC-futures/test_expect_pragma.py
Normal file
@ -0,0 +1,193 @@
|
||||
import collections
|
||||
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)
|
||||
|
||||
|
||||
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]
|
||||
|
||||
|
||||
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, 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 skip_if_partial_match(self, actual, expect, ignore_error_message):
|
||||
if not ignore_error_message:
|
||||
return False
|
||||
|
||||
# TODO: Deal with undefined behavior here?
|
||||
|
||||
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)
|
105
TPfutures/MiniC-futures/test_futures.py
Executable file
105
TPfutures/MiniC-futures/test_futures.py
Executable file
@ -0,0 +1,105 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
import glob
|
||||
import subprocess
|
||||
import re
|
||||
from test_expect_pragma import (
|
||||
TestExpectPragmas, cat, testinfo,
|
||||
env_bool_variable, env_str_variable
|
||||
)
|
||||
|
||||
"""
|
||||
Usage:
|
||||
python3 test_futur.py
|
||||
(or make test)
|
||||
"""
|
||||
|
||||
"""
|
||||
CAP, 2020
|
||||
Unit test infrastructure for testing futures:
|
||||
1) compare the actual output to the expected one (in comments)
|
||||
2) compare the actual output to the one obtained by simulation
|
||||
"""
|
||||
|
||||
DISABLE_TYPECHECK = False
|
||||
TYPECHECK_ONLY = False
|
||||
|
||||
HERE = os.path.dirname(os.path.realpath(__file__))
|
||||
if HERE == os.path.realpath('.'):
|
||||
HERE = '.'
|
||||
TEST_DIR = HERE
|
||||
IMPLEM_DIR = HERE
|
||||
MINIC_FUT = os.path.join(IMPLEM_DIR, 'MiniCC.py')
|
||||
|
||||
ALL_FILES = glob.glob(os.path.join(TEST_DIR, 'tests/**/[a-zA-Z]*.c'),
|
||||
recursive=True)
|
||||
|
||||
GCC = 'gcc'
|
||||
|
||||
if 'TEST_FILES' in os.environ:
|
||||
ALL_FILES = glob.glob(os.environ['TEST_FILES'], recursive=True)
|
||||
|
||||
|
||||
class TestFuture(TestExpectPragmas):
|
||||
|
||||
# Not in test_expect_pragma to get assertion rewritting
|
||||
def assert_equal(self, actual, expected):
|
||||
if TYPECHECK_ONLY and expected.exitcode == 0:
|
||||
# Compiler does not fail => no output expected
|
||||
assert actual.output == "", \
|
||||
("Compiler unexpectedly generated some"
|
||||
"output with --disable-codegen")
|
||||
assert actual.exitcode == 0, \
|
||||
"Compiler unexpectedly failed with --disable-codegen"
|
||||
return
|
||||
if DISABLE_TYPECHECK and expected.exitcode != 0:
|
||||
# Test should fail at typecheck, and we don't do
|
||||
# typechecking => nothing to check.
|
||||
pytest.skip("Test that doesn't typecheck with --disable-typecheck")
|
||||
if expected.output is not None and actual.output is not None:
|
||||
assert actual.output == expected.output, \
|
||||
"Output of the program is incorrect."
|
||||
assert actual.exitcode == expected.exitcode, \
|
||||
"Exit code of the compiler is incorrect"
|
||||
assert actual.execcode == expected.execcode, \
|
||||
"Exit code of the execution is incorrect"
|
||||
|
||||
def c2c(self, file):
|
||||
return self.run_command(['python3', MINIC_FUT, file])
|
||||
|
||||
def compile_with_gcc(self, file, output_name):
|
||||
print("Compiling with GCC...")
|
||||
result = self.run_command(
|
||||
[GCC, '-Iinclude', '-Ilib', '-x', 'c', file, "lib/futurelib.c",
|
||||
'--output=' + output_name, '-lpthread'])
|
||||
print(result.output)
|
||||
print("Compiling with GCC... DONE")
|
||||
return result
|
||||
|
||||
def compile_and_run(self, file):
|
||||
basename, _ = os.path.splitext(file)
|
||||
rw_name = basename + '.cfut'
|
||||
exec_name = basename + '.out'
|
||||
print("File: " + rw_name)
|
||||
resgcc = self.compile_with_gcc(rw_name, exec_name)
|
||||
if resgcc.exitcode != 0:
|
||||
return resgcc._replace(exitcode=1, output=None)
|
||||
res2 = self.run_command(exec_name, scope="runtime")
|
||||
return res2
|
||||
|
||||
@pytest.mark.parametrize('filename', ALL_FILES)
|
||||
def test_future(self, filename):
|
||||
expect = self.get_expect(filename)
|
||||
c2csuccess = self.c2c(filename)
|
||||
if c2csuccess.exitcode == 0:
|
||||
actual = self.compile_and_run(filename)
|
||||
else:
|
||||
actual = c2csuccess
|
||||
self.assert_equal(actual, expect)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(sys.argv)
|
38
TPfutures/MiniC-futures/tests/provided/bad_type_fut1.c
Normal file
38
TPfutures/MiniC-futures/tests/provided/bad_type_fut1.c
Normal file
@ -0,0 +1,38 @@
|
||||
#include "compat.h"
|
||||
|
||||
// Call future
|
||||
int slow(int x)
|
||||
{
|
||||
int i;
|
||||
i=0;
|
||||
while (i<1000) { i=i+1 ; x=2*i+x;}
|
||||
return x;
|
||||
}
|
||||
|
||||
int summ(int x)
|
||||
{
|
||||
int ret,i;
|
||||
if (x == 1)
|
||||
ret=1;
|
||||
else
|
||||
{
|
||||
ret = x + summ(x - 1);
|
||||
i=0;
|
||||
while (i<100) { i=i+1 ; x=slow(2);}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
int main()
|
||||
{
|
||||
int val;
|
||||
|
||||
val=Async(summ,1);
|
||||
println_int(val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXITCODE 2
|
||||
// EXPECTED
|
||||
// In function main: Line 29 col 4: type mismatch for val: integer and futinteger
|
||||
|
20
TPfutures/MiniC-futures/tests/provided/test_fut0.c
Normal file
20
TPfutures/MiniC-futures/tests/provided/test_fut0.c
Normal file
@ -0,0 +1,20 @@
|
||||
#include "compat.h"
|
||||
|
||||
// Call future
|
||||
|
||||
int functi(int x){
|
||||
int y;
|
||||
y=x;
|
||||
y = 42;
|
||||
println_int(0);
|
||||
return y;
|
||||
}
|
||||
|
||||
int main(){
|
||||
futint fval;
|
||||
fval = Async(functi,123);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 0
|
22
TPfutures/MiniC-futures/tests/provided/test_fut1.c
Normal file
22
TPfutures/MiniC-futures/tests/provided/test_fut1.c
Normal file
@ -0,0 +1,22 @@
|
||||
#include "compat.h"
|
||||
|
||||
// Call future
|
||||
|
||||
int functi(int x){
|
||||
int y;
|
||||
y=x;
|
||||
y = 42;
|
||||
return y;
|
||||
}
|
||||
|
||||
int main(){
|
||||
futint fval;
|
||||
int val;
|
||||
fval = Async(functi,123);
|
||||
val = Get(fval);
|
||||
println_int(val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 42
|
41
TPfutures/MiniC-futures/tests/provided/test_fut2.c
Normal file
41
TPfutures/MiniC-futures/tests/provided/test_fut2.c
Normal file
@ -0,0 +1,41 @@
|
||||
#include "compat.h"
|
||||
|
||||
// Call future
|
||||
|
||||
int summ(int x)
|
||||
{
|
||||
int ret;
|
||||
if (x == 1)
|
||||
ret=1;
|
||||
else
|
||||
{
|
||||
ret=x + summ(x - 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int useFuture(futint f)
|
||||
{
|
||||
int x;
|
||||
x=Get(f);
|
||||
return x+1;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
int val,x;
|
||||
futint f,g;
|
||||
|
||||
f=Async(summ,15);
|
||||
g=Async(summ,16);
|
||||
|
||||
val=Get(f)+Get(g)+useFuture(g);
|
||||
println_int(val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// EXPECTED
|
||||
// 393
|
56
TPfutures/MiniC-futures/tests/provided/test_fut4.c
Normal file
56
TPfutures/MiniC-futures/tests/provided/test_fut4.c
Normal file
@ -0,0 +1,56 @@
|
||||
#include "compat.h"
|
||||
|
||||
// Call future
|
||||
|
||||
int slow(int x)
|
||||
{
|
||||
int i,t;
|
||||
i=0;
|
||||
t=0;
|
||||
while (i<x*1000) { i=i+1 ; t=t+2*i+x;}
|
||||
return t;
|
||||
}
|
||||
|
||||
int summ(int x)
|
||||
{
|
||||
int ret;
|
||||
int i;
|
||||
if (x == 1)
|
||||
ret=1;
|
||||
else
|
||||
{
|
||||
ret = x + summ(x - 1);
|
||||
i=0;
|
||||
while (i<100) { i=i+1 ; x=slow(20);}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int summandprint(int x)
|
||||
{
|
||||
int ret;
|
||||
ret=summ(x);
|
||||
println_int(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
int val;
|
||||
futint f,g;
|
||||
|
||||
f=Async(summandprint,500);
|
||||
g=Async(summandprint,2);
|
||||
|
||||
val=Get(f)+Get(g);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// EXPECTED
|
||||
// 3
|
||||
// 125250
|
BIN
TPfutures/tpfutures.pdf
Normal file
BIN
TPfutures/tpfutures.pdf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user