Skip to content

Commit

Permalink
Apply some Pylint fixes, Part 1: Clean up parser (#1826)
Browse files Browse the repository at this point in the history
Clean up the `parser/` folder by fixing missing fstrings. As a result
docstrings are also improved.
  • Loading branch information
EmilyBourne committed Apr 11, 2024
1 parent cd6f30f commit dcfdc6e
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 65 deletions.
48 changes: 30 additions & 18 deletions pyccel/parser/base.py
Expand Up @@ -89,8 +89,8 @@ def get_filename_from_import(module, input_folder=''):
while filename.startswith('/'):
filename = folder_above + filename[1:]

filename_pyh = '{}.pyh'.format(filename)
filename_py = '{}.py'.format(filename)
filename_pyh = f'{filename}.pyh'
filename_py = f'{filename}.py'

poss_filename_pyh = os.path.join( input_folder, filename_pyh )
poss_filename_py = os.path.join( input_folder, filename_py )
Expand All @@ -106,8 +106,8 @@ def get_filename_from_import(module, input_folder=''):

source = """.""".join(i for i in module.split(""".""")[:-1])
_module = module.split(""".""")[-1]
filename_pyh = '{}.pyh'.format(_module)
filename_py = '{}.py'.format(_module)
filename_pyh = f'{_module}.pyh'
filename_py = f'{_module}.py'

try:
package = importlib.import_module(source)
Expand Down Expand Up @@ -402,6 +402,8 @@ def exit_loop_scope(self):

def create_new_class_scope(self, name, **kwargs):
"""
Create a new scope for a Python class.
Create a new Scope object for a Python class with the given name,
and attach any decorators' information to the scope. The new scope is
a child of the current one, and can be accessed from the dictionary of
Expand All @@ -416,6 +418,13 @@ def create_new_class_scope(self, name, **kwargs):
name : str
Function's name, used as a key to retrieve the new scope.
**kwargs : dict
A dictionary containing any additional arguments of the new scope.
Returns
-------
Scope
The scope for the class.
"""
child = self.scope.new_child_scope(name, **kwargs)
self._scope = child
Expand All @@ -431,11 +440,13 @@ def dump(self, filename=None):
"""
Dump the current ast using Pickle.
Parameters
----------
filename: str
output file name. if not given `name.pyccel` will be used and placed
in the Pyccel directory ($HOME/.pyccel)
Dump the current ast using Pickle.
Parameters
----------
filename : str
Output file name. if not given `name.pyccel` will be used and placed
in the Pyccel directory ($HOME/.pyccel).
"""
if self._created_from_pickle:
return
Expand All @@ -450,7 +461,7 @@ def dump(self, filename=None):
if ext != '.pyh':
return

name = '{}.pyccel'.format(name)
name = f'{name}.pyccel'
filename = os.path.join(path, name)
# check extension

Expand All @@ -460,7 +471,6 @@ def dump(self, filename=None):
import pickle
import hashlib

# print('>>> home = ', os.environ['HOME'])
# ...

# we are only exporting the AST.
Expand All @@ -478,13 +488,15 @@ def dump(self, filename=None):
warnings.warn("Can't pickle files on a read-only system. Please run `sudo pyccel-init`")

def load(self, filename=None):
""" Load the current ast using Pickle.
"""
Load the current ast using Pickle.
Parameters
----------
filename: str
output file name. if not given `name.pyccel` will be used and placed
in the Pyccel directory ($HOME/.pyccel)
Load the current ast using Pickle.
Parameters
----------
filename : str, optional
The name of the pickled file. if not given `name.pyccel` will be used.
"""

# ...
Expand All @@ -500,7 +512,7 @@ def load(self, filename=None):
if ext != '.pyh':
return

name = '{}.pyccel'.format(name)
name = f'{name}.pyccel'
filename = os.path.join(path, name)

if not filename.split(""".""")[-1] == 'pyccel':
Expand Down
61 changes: 42 additions & 19 deletions pyccel/parser/parser.py
Expand Up @@ -17,6 +17,19 @@
# TODO [AR, 18.11.2018] to be modified as a function
# TODO [YG, 28.01.2020] maybe pass filename to the parse method?
class Parser(object):
"""
A wrapper class which handles dependencies between the syntactic and semantic parsers.
A wrapper class which handles dependencies between the syntactic and semantic parsers.
Parameters
----------
filename : str
The name of the file being translated.
**kwargs : dict
Any keyword arguments for BasicParser.
"""

def __init__(self, filename, **kwargs):

Expand Down Expand Up @@ -181,24 +194,26 @@ def append_son(self, son):
self._sons.append(son)

def parse_sons(self, d_parsers_by_filename, verbose=False):
"""Recursive algorithm for syntax analysis on a given file and its
"""
Parse the files on which this file is dependent.
Recursive algorithm for syntax analysis on a given file and its
dependencies.
This function always terminates with an dict that contains parsers
for all involved files.
Parameters
----------
d_parsers_by_filename : dict
A dictionary of parsed sons.
Parameters
----------
d_parsers_by_filename : dict
A dictionary of parsed sons.
verbose: bool
Determine the verbosity.
Results
-------
d_parsers: dict
The updated dictionary of parsed sons.
verbose : bool, default=False
Set the verbosity.
Returns
-------
dict
The updated dictionary of parsed sons.
"""

imports = self.imports
Expand All @@ -207,7 +222,7 @@ def parse_sons(self, d_parsers_by_filename, verbose=False):
not_treated = [i for i in source_to_filename.values() if i not in treated]
for filename in not_treated:
if verbose:
print ('>>> treating :: {}'.format(filename))
print ('>>> treating :: ', filename)

# get the absolute path corresponding to source
if filename in d_parsers_by_filename:
Expand All @@ -228,25 +243,33 @@ def parse_sons(self, d_parsers_by_filename, verbose=False):

return d_parsers

def _annotate_sons(self, **settings):
def _annotate_sons(self, verbose = False):
"""
Annotate any dependencies of the file currently being parsed.
verbose = settings.pop('verbose', False)
Annotate any dependencies of the file currently being parsed.
Parameters
----------
verbose : bool, default=False
Indicates if the treatment should be run in verbose mode.
"""

# we first treat sons that have no imports

for p in self.sons:
if not p.sons:
if verbose:
print ('>>> treating :: {}'.format(p.filename))
p.annotate(**settings)
print ('>>> treating :: ', p.filename)
p.annotate()

# finally we treat the remaining sons recursively

for p in self.sons:
if p.sons:
if verbose:
print ('>>> treating :: {}'.format(p.filename))
p.annotate(**settings)
print ('>>> treating :: ', p.filename)
p.annotate()

#==============================================================================

Expand Down
81 changes: 55 additions & 26 deletions pyccel/parser/scope.py
Expand Up @@ -127,7 +127,7 @@ def new_child_scope(self, name, **kwargs):
"""
ps = kwargs.pop('parent_scope', self)
if ps is not self:
raise ValueError("A child of {} cannot have a parent {}".format(self, ps))
raise ValueError(f"A child of {self} cannot have a parent {ps}")

child = Scope(name=name, **kwargs, parent_scope = self)

Expand Down Expand Up @@ -308,21 +308,17 @@ def create_new_loop_scope(self):
return new_scope

def insert_variable(self, var, name = None):
""" Add a variable to the current scope
"""
Add a variable to the current scope.
Add a variable to the current scope.
Parameters
----------
var : Variable
The variable to be inserted into the current scope
name : str
The name of the variable in the python code
Default : var.name
python_scope : bool
If true then we assume that python scoping applies.
In this case variables declared in loops exist beyond
the end of the loop. Otherwise variables may be local
to loops
Default : True
var : Variable
The variable to be inserted into the current scope.
name : str, default=var.name
The name of the variable in the Python code.
"""
if var.name == '_':
raise ValueError("A temporary variable should have a name generated by Scope.get_new_name")
Expand All @@ -336,7 +332,7 @@ def insert_variable(self, var, name = None):
self.parent_scope.insert_variable(var, name)
else:
if name in self._locals['variables']:
raise RuntimeError('New variable {} already exists in scope'.format(name))
raise RuntimeError(f'New variable {name} already exists in scope')
if name == '_':
self._temporary_variables.append(var)
else:
Expand Down Expand Up @@ -645,17 +641,24 @@ def get_new_name(self, current_name = None):

def get_temporary_variable(self, dtype_or_var, name = None, **kwargs):
"""
Get a temporary variable
Get a temporary variable.
Get a temporary variable.
Parameters
----------
dtype_or_var : str, DataType, Variable
In the case of a string of DataType: The type of the Variable to be created
In the case of a Variable: a Variable which will be cloned to set all the Variable properties
name : str
The requested name for the new variable
kwargs : dict
See Variable keyword arguments
In the case of a Variable: a Variable which will be cloned to set all the Variable properties.
name : str, optional
The requested name for the new variable.
**kwargs : dict
See Variable keyword arguments.
Returns
-------
Variable
The temporary variable.
"""
assert isinstance(name, (str, type(None)))
name = self.get_new_name(name)
Expand All @@ -667,8 +670,21 @@ def get_temporary_variable(self, dtype_or_var, name = None, **kwargs):
return var

def get_expected_name(self, start_name):
""" Get a name with no collisions, ideally the provided name.
The provided name should already exist in the symbols
"""
Get a name with no collisions.
Get a name with no collisions, ideally the provided name.
The provided name should already exist in the symbols.
Parameters
----------
start_name : str
The name which was used in the Python code.
Returns
-------
PyccelSymbol
The name which will be used in the generated code.
"""
if start_name == '_':
return self.get_new_name()
Expand All @@ -677,7 +693,7 @@ def get_expected_name(self, start_name):
elif self.parent_scope:
return self.parent_scope.get_expected_name(start_name)
else:
raise RuntimeError("{} does not exist in scope".format(start_name))
raise RuntimeError(f"{start_name} does not exist in scope")

def create_product_loop_scope(self, inner_scope, n_loops):
""" Create a n_loops loop scopes such that the innermost loop
Expand Down Expand Up @@ -749,15 +765,28 @@ def add_son(self, name, son):
self._sons_scopes[name] = son

def get_python_name(self, name):
""" Get the name used in the original python code from the
name used by the variable
"""
Get the name used in the original Python code.
Get the name used in the original Python code from the name used
by the variable that was created in the parser.
Parameters
----------
name : PyccelSymbol | str
The name of the Variable in the generated code.
Returns
-------
str
The name of the Variable in the original code.
"""
if name in self._original_symbol:
return self._original_symbol[name]
elif self.parent_scope:
return self.parent_scope.get_python_name(name)
else:
raise RuntimeError("Can't find {} in scope".format(name))
raise RuntimeError(f"Can't find {name} in scope")

@property
def python_names(self):
Expand Down
4 changes: 2 additions & 2 deletions pyccel/parser/syntactic.py
Expand Up @@ -541,7 +541,7 @@ def _visit_Constant(self, stmt):
return LiteralEllipsis()

else:
raise NotImplementedError('Constant type {} not recognised'.format(type(stmt.value)))
raise NotImplementedError(f'Constant type {type(stmt.value)} not recognised')

def _visit_Name(self, stmt):
name = PyccelSymbol(stmt.id)
Expand Down Expand Up @@ -1109,7 +1109,7 @@ def _visit_Call(self, stmt):
func_attr = FunctionCall(func.name[-1], args)
func = DottedName(*func.name[:-1], func_attr)
else:
raise NotImplementedError(' Unknown function type {}'.format(str(type(func))))
raise NotImplementedError(f' Unknown function type {type(func)}')

return func

Expand Down

0 comments on commit dcfdc6e

Please sign in to comment.