Skip to content

Commit

Permalink
Merge branch 'devel' into issue#1399
Browse files Browse the repository at this point in the history
  • Loading branch information
smhah committed Jun 2, 2023
2 parents 2ab1443 + d6fea05 commit 19dec02
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 151 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
### Fixed

- #1302 : Raise error message in case of empty class
- #1407 : Raise an error if file name matches a Python built-in module.
- #929 : Allow optional variables when compiling with intel or nvidia.
- #1117 : Allow non-contiguous arrays to be passed to Fortran code.
- #1399 : Fix enumerate and zip in comprehension list
Expand Down
116 changes: 46 additions & 70 deletions pyccel/codegen/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os
import sys
import shutil
from pathlib import Path

from pyccel.errors.errors import Errors, PyccelError
from pyccel.errors.errors import PyccelSyntaxError, PyccelSemanticError, PyccelCodegenError
Expand All @@ -22,6 +23,7 @@
from pyccel.codegen.python_wrapper import create_shared_library
from pyccel.naming import name_clash_checkers
from pyccel.utilities.stage import PyccelStage
from pyccel.ast.utilities import python_builtin_libs
from pyccel.parser.scope import Scope

from .compiling.basic import CompileObj
Expand Down Expand Up @@ -57,88 +59,62 @@ def execute_pyccel(fname, *,
output_name = None,
compiler_export_file = None):
"""
Carries out the main steps required to execute pyccel
Run Pyccel on the provided code.
Carry out the main steps required to execute Pyccel:
- Parses the python file (syntactic stage)
- Annotates the abstract syntax tree (semantic stage)
- Generates the translated file(s) (codegen stage)
- Compiles the files to generate an executable and/or a shared library
- Compiles the files to generate an executable and/or a shared library.
Parameters
----------
fname : str
Name of python file to be translated
syntax_only : bool
Boolean indicating whether the pipeline should stop
after the syntax stage
Default : False
semantic_only : bool
Boolean indicating whether the pipeline should stop
after the semantic stage
Default : False
convert_only : bool
Boolean indicating whether the pipeline should stop
after the codegen stage
Default : False
verbose : bool
Boolean indicating whether debugging messages should be printed
Default : False
folder : str
Path to the working directory
Default : folder containing the file to be translated
language : str
The language which pyccel is translating to
Default : fortran
compiler : str
The compiler used to compile the generated files
Default : GNU
fflags : str
The flags passed to the compiler
Default : provided by Compiler
wrapper_flags : str
The flags passed to the compiler to compile the c wrapper
Default : provided by Compiler
includes : list
list of include directories paths
libdirs : list
list of paths to directories containing the required libraries
modules : list
list of files which must also be compiled in order to compile this module
libs : list
list of required libraries
debug : bool
Boolean indicating whether the file should be compiled in debug mode
(currently this only implies that the flag -fcheck=bounds is added)
Default : False
accelerators : iterable
Tool used to accelerate the code (e.g. openmp openacc)
output_name : str
Name of the generated module
Default : Same name as the file which was translated
export_compile_info : str
Name of the json file to which compiler information is exported
Default : None
fname : str
Name of the Python file to be translated.
syntax_only : bool, optional
Indicates whether the pipeline should stop after the syntax stage. Default is False.
semantic_only : bool, optional
Indicates whether the pipeline should stop after the semantic stage. Default is False.
convert_only : bool, optional
Indicates whether the pipeline should stop after the codegen stage. Default is False.
verbose : bool, optional
Indicates whether debugging messages should be printed. Default is False.
folder : str, optional
Path to the working directory. Default is the folder containing the file to be translated.
language : str, optional
The target language Pyccel is translating to. Default is 'fortran'.
compiler : str, optional
The compiler used to compile the generated files. Default is 'GNU'.
fflags : str, optional
The flags passed to the compiler. Default is provided by the Compiler.
wrapper_flags : str, optional
The flags passed to the compiler to compile the C wrapper. Default is provided by the Compiler.
includes : list, optional
List of include directory paths.
libdirs : list, optional
List of paths to directories containing the required libraries.
modules : list, optional
List of files that must be compiled in order to compile this module.
libs : list, optional
List of required libraries.
debug : bool, optional
Indicates whether the file should be compiled in debug mode. Default is False.
(Currently, this only implies that the flag -fcheck=bounds is added.).
accelerators : iterable, optional
Tool used to accelerate the code (e.g., OpenMP, OpenACC).
output_name : str, optional
Name of the generated module. Default is the same name as the translated file.
compiler_export_file : str, optional
Name of the JSON file to which compiler information is exported. Default is None.
"""
if fname.endswith('.pyh'):
syntax_only = True
if verbose:
print("Header file recognised, stopping after syntactic stage")

if Path(fname).stem in python_builtin_libs:
raise ValueError(f"File called {os.path.basename(fname)} has the same name as a Python built-in package and can't be imported from Python. See #1402")

# Reset Errors singleton before parsing a new file
errors = Errors()
errors.reset()
Expand Down
109 changes: 28 additions & 81 deletions pyccel/parser/semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2444,47 +2444,6 @@ def _visit_Assign(self, expr):
bounding_box=(self._current_fst_node.lineno, self._current_fst_node.col_offset),
severity='fatal')

elif isinstance(rhs, DottedVariable):
var = rhs.rhs
name = _get_name(var)
macro = self.scope.find(name, 'macros')
if macro is None:
rhs = self._visit(rhs)
else:
master = macro.master
if isinstance(macro, MacroVariable):
rhs = master
else:
# If macro is function, create left-hand side variable
if isinstance(master, FunctionDef) and master.results:
d_var = self._infer_type(master.results[0].var)
dtype = d_var.pop('datatype')
lhs = Variable(dtype, lhs.name, **d_var, is_temp=lhs.is_temp)
var = self.check_for_variable(lhs.name)
if var is None:
self.scope.insert_variable(lhs)

name = macro.name
if not sympy_iterable(lhs):
lhs = [lhs]
results = []
for a in lhs:
_name = _get_name(a)
var = self.get_variable(_name)
results.append(var)

args = rhs.rhs.args
args = [rhs.lhs] + list(args)
args = [self._visit(i) for i in args]

args = macro.apply(args, results=results)

# Distinguish between function
if master.results:
return Assign(lhs[0], FunctionCall(master, args, self._current_function))
else:
return FunctionCall(master, args, self._current_function)

else:
rhs = self._visit(rhs)

Expand Down Expand Up @@ -2525,48 +2484,36 @@ def _visit_Assign(self, expr):

elif isinstance(rhs, FunctionCall):
func = rhs.funcdef
if isinstance(func, FunctionDef):
results = func.results
if results:
if len(results)==1:
d_var = self._infer_type(results[0].var)
else:
d_var = self._infer_type(PythonTuple(*[r.var for r in results]))
elif expr.lhs.is_temp:
return rhs
results = func.results
if results:
if len(results)==1:
d_var = self._infer_type(results[0].var)
else:
raise NotImplementedError("Cannot assign result of a function without a return")

# case of elemental function
# if the input and args of func do not have the same shape,
# then the lhs must be already declared
if func.is_elemental:
# we first compare the funcdef args with the func call
# args
# d_var = None
func_args = func.arguments
call_args = rhs.args
f_ranks = [x.var.rank for x in func_args]
c_ranks = [x.value.rank for x in call_args]
same_ranks = [x==y for (x,y) in zip(f_ranks, c_ranks)]
if not all(same_ranks):
assert(len(c_ranks) == 1)
arg = call_args[0].value
d_var['shape' ] = arg.shape
d_var['rank' ] = arg.rank
d_var['memory_handling'] = arg.memory_handling
d_var['order' ] = arg.order

elif isinstance(func, Interface):
d_var = [self._infer_type(i.var) for i in
func.functions[0].results]

# TODO imporve this will not work for
# the case of different results types
d_var[0]['datatype'] = rhs.dtype

d_var = self._infer_type(PythonTuple(*[r.var for r in results]))
elif expr.lhs.is_temp:
return rhs
else:
d_var = self._infer_type(rhs)
raise NotImplementedError("Cannot assign result of a function without a return")

# case of elemental function
# if the input and args of func do not have the same shape,
# then the lhs must be already declared
if func.is_elemental:
# we first compare the funcdef args with the func call
# args
# d_var = None
func_args = func.arguments
call_args = rhs.args
f_ranks = [x.var.rank for x in func_args]
c_ranks = [x.value.rank for x in call_args]
same_ranks = [x==y for (x,y) in zip(f_ranks, c_ranks)]
if not all(same_ranks):
assert(len(c_ranks) == 1)
arg = call_args[0].value
d_var['shape' ] = arg.shape
d_var['rank' ] = arg.rank
d_var['memory_handling'] = arg.memory_handling
d_var['order' ] = arg.order

elif isinstance(rhs, NumpyTranspose):
d_var = self._infer_type(rhs)
Expand Down
10 changes: 10 additions & 0 deletions tests/pyccel/test_pyccel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import shutil
import sys
import re
import random
import pytest
import numpy as np
from pyccel.codegen.pipeline import execute_pyccel
from pyccel.ast.utilities import python_builtin_libs

#==============================================================================
# UTILITIES
Expand Down Expand Up @@ -995,3 +998,10 @@ def test_json():
dict_2 = json.load(f)

assert dict_1 == dict_2

#------------------------------------------------------------------------------
def test_reserved_file_name():
with pytest.raises(ValueError) as exc_info:
libname = str(random.choice(tuple(python_builtin_libs))) + ".py" # nosec B311
execute_pyccel(fname=libname)
assert str(exc_info.value) == f"File called {libname} has the same name as a Python built-in package and can't be imported from Python. See #1402"

0 comments on commit 19dec02

Please sign in to comment.