Skip to content

Commit 97bdb73

Browse files
authored
Add C support for local string declarations (pyccel#2064)
Add C support for local string declarations. This is a first step towards allowing string keys for dictionaries. Fixes pyccel#2061 **Commit Summary** - Handle strings in loop unravelling - Allow extensions to depend on stc folders by using a recognisable dependency that can be swapped for the correct directory - Fix declaration of strings in C - Add support for `PyccelArrayShapeElement` of a string in C - Remove double line break after deallocation - Add support for string initialisation - Correct the warning message for array reallocation (maybe this should be removed for non-arrays) - Ensure strings are allocated in the semantic parser. - Create a `cstr.c` file to compile string functions independently (avoids duplicate definitions) - Activate tests - Add a leak test for string reallocation
1 parent e5bc912 commit 97bdb73

File tree

15 files changed

+130
-86
lines changed

15 files changed

+130
-86
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ All notable changes to this project will be documented in this file.
5555
- #2016 : Add support for translating arithmetic magic methods (methods cannot yet be used from Python).
5656
- #1980 : Extend The C support for min and max to more than two variables
5757
- #2081 : Add support for multi operator expressions
58+
- #2061 : Add C support for string declarations.
5859
- Add support for inhomogeneous tuple annotations.
5960
- #1834 : Add support for `@property` decorator.
6061
- \[INTERNALS\] Add abstract class `SetMethod` to handle calls to various set methods.

pyccel/ast/utilities.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,12 @@ def collect_loops(block, indices, new_index, language_has_vectors = False, resul
425425
and not isinstance(f, (NumpyTranspose))))
426426
for line in block:
427427

428-
if (isinstance(line, Assign) and
428+
if isinstance(line, Assign) and isinstance(line.lhs.class_type, StringType):
429+
# Save line in top level (no for loop)
430+
result.append(line)
431+
current_level = 0
432+
433+
elif (isinstance(line, Assign) and
429434
not isinstance(line.rhs, (array_creator_types, Nil)) and # not creating array
430435
not line.rhs.get_attribute_nodes(array_creator_types) and # not creating array
431436
not is_function_call(line.rhs)): # not a basic function call

pyccel/codegen/compiling/basic.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,7 @@ def __init__(self,
9191
self._lock_source = FileLock(self.source+'.lock')
9292

9393
self._flags = list(flags)
94-
if has_target_file:
95-
self._includes = set([folder, *includes])
96-
else:
97-
self._includes = set(includes)
94+
self._includes = set([folder, *includes])
9895
self._libs = list(libs)
9996
self._libdirs = set(libdirs)
10097
self._accelerators = set(accelerators)
@@ -117,9 +114,8 @@ def reset_folder(self, folder):
117114
folder : str
118115
The new folder where the source file can be found.
119116
"""
120-
if self.has_target_file:
121-
self._includes.remove(self._folder)
122-
self._includes.add(folder)
117+
self._includes.remove(self._folder)
118+
self._includes.add(folder)
123119

124120
self._file = os.path.join(folder, os.path.basename(self._file))
125121
self._lock_source = FileLock(self.source+'.lock')

pyccel/codegen/pipeline.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ def handle_error(stage):
311311
# pass
312312
#------------------------------------------------------
313313
try:
314-
manage_dependencies(codegen.printer, src_compiler, pyccel_dirpath, mod_obj,
314+
manage_dependencies(codegen.printer.get_additional_imports(), src_compiler, pyccel_dirpath, mod_obj,
315315
language, verbose, convert_only)
316316
except NotImplementedError as error:
317317
errors.report(f'{error}\n'+PYCCEL_RESTRICTION_TODO,

pyccel/codegen/printing/ccode.py

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@
247247
'stdio',
248248
"inttypes",
249249
'stdbool',
250-
'assert']}
250+
'assert',
251+
'stc/cstr']}
251252

252253
import_header_guard_prefix = {'stc/hset' : '_TOOLS_SET',
253254
'stc/vec' : '_TOOLS_LIST',
@@ -309,7 +310,6 @@ class CCodePrinter(CodePrinter):
309310
(PrimitiveIntegerType(),8) : LiteralString("%") + CMacro('PRId64'),
310311
(PrimitiveIntegerType(),2) : LiteralString("%") + CMacro('PRId16'),
311312
(PrimitiveIntegerType(),1) : LiteralString("%") + CMacro('PRId8'),
312-
StringType() : '%s',
313313
}
314314

315315
def __init__(self, filename, prefix_module = None):
@@ -1039,7 +1039,7 @@ def _print_Import(self, expr):
10391039
f'#include <{stc_extension_mapping[source]}.h>\n',
10401040
f'#endif // {header_guard}\n\n'))
10411041
return code
1042-
elif source.startswith('stc/'):
1042+
elif source != 'stc/cstr' and (source.startswith('stc/') or source in import_header_guard_prefix):
10431043
code = ''
10441044
for t in expr.target:
10451045
class_type = t.object.class_type
@@ -1130,6 +1130,13 @@ def get_print_format_and_arg(self, var):
11301130
except KeyError:
11311131
errors.report(f"Printing {var.dtype} type is not supported currently", severity='fatal')
11321132
arg = self._print(var)
1133+
elif isinstance(var.dtype, StringType):
1134+
if isinstance(var, Variable):
1135+
var_obj = self._print(ObjectAddress(var))
1136+
arg = f'cstr_str({var_obj})'
1137+
else:
1138+
arg = self._print(var)
1139+
arg_format = '%s'
11331140
else:
11341141
try:
11351142
arg_format = self.type_to_format[var.dtype]
@@ -1302,6 +1309,9 @@ def get_c_type(self, dtype):
13021309
i_type = f'{container_type}_{key_type}_{val_type}'
13031310
self.add_import(Import(f'stc/{container_type}', AsName(VariableTypeAnnotation(dtype), i_type)))
13041311
return i_type
1312+
elif isinstance(dtype, StringType):
1313+
self.add_import(c_imports['stc/cstr'])
1314+
return 'cstr'
13051315
else:
13061316
key = dtype
13071317

@@ -1377,11 +1387,11 @@ def get_declare_type(self, expr):
13771387
rank = expr.rank
13781388

13791389
if rank > 0:
1380-
if isinstance(expr.class_type, (HomogeneousSetType, HomogeneousListType, DictType)):
1381-
dtype = self.get_c_type(expr.class_type)
1382-
elif isinstance(expr.class_type, CStackArray):
1383-
return self.get_c_type(expr.class_type.element_type)
1384-
elif isinstance(expr.class_type, (HomogeneousTupleType, NumpyNDArrayType)):
1390+
if isinstance(class_type, (HomogeneousSetType, HomogeneousListType, DictType, StringType)):
1391+
dtype = self.get_c_type(class_type)
1392+
elif isinstance(class_type, CStackArray):
1393+
return self.get_c_type(class_type.element_type)
1394+
elif isinstance(class_type, (HomogeneousTupleType, NumpyNDArrayType)):
13851395
if expr.rank > 15:
13861396
errors.report(UNSUPPORTED_ARRAY_RANK, symbol=expr, severity='fatal')
13871397
self.add_import(c_imports['ndarrays'])
@@ -1699,15 +1709,18 @@ def _print_PyccelArrayShapeElement(self, expr):
16991709
c_type = self.get_c_type(arg.class_type)
17001710
arg_code = self._print(ObjectAddress(arg))
17011711
return f'{c_type}_size({arg_code})'
1712+
elif isinstance(arg.class_type, StringType):
1713+
arg_code = self._print(ObjectAddress(arg))
1714+
return f'cstr_size({arg_code})'
17021715
else:
17031716
raise NotImplementedError(f"Don't know how to represent shape of object of type {arg.class_type}")
17041717

17051718
def _print_Allocate(self, expr):
17061719
free_code = ''
17071720
variable = expr.variable
1708-
if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
1721+
if isinstance(variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType, StringType)):
17091722
if expr.status in ('allocated', 'unknown'):
1710-
free_code = f'{self._print(Deallocate(variable))}\n'
1723+
free_code = f'{self._print(Deallocate(variable))}'
17111724
if expr.shape[0] is None:
17121725
return free_code
17131726
size = self._print(expr.shape[0])
@@ -1718,7 +1731,7 @@ def _print_Allocate(self, expr):
17181731
elif expr.alloc_type == 'resize':
17191732
return f'{container_type}_resize({variable_address}, {size}, {0});\n'
17201733
return free_code
1721-
if isinstance(variable.class_type, (NumpyNDArrayType, HomogeneousTupleType)):
1734+
elif isinstance(variable.class_type, (NumpyNDArrayType, HomogeneousTupleType)):
17221735
#free the array if its already allocated and checking if its not null if the status is unknown
17231736
if (expr.status == 'unknown'):
17241737
shape_var = DottedVariable(VoidType(), 'shape', lhs = variable)
@@ -1755,7 +1768,7 @@ def _print_Allocate(self, expr):
17551768
raise NotImplementedError(f"Allocate not implemented for {variable.class_type}")
17561769

17571770
def _print_Deallocate(self, expr):
1758-
if isinstance(expr.variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType)):
1771+
if isinstance(expr.variable.class_type, (HomogeneousListType, HomogeneousSetType, DictType, StringType)):
17591772
if expr.variable.is_alias:
17601773
return ''
17611774
variable_address = self._print(ObjectAddress(expr.variable))
@@ -2306,11 +2319,13 @@ def _print_Assign(self, expr):
23062319
return self.copy_NumpyArray_Data(expr)
23072320
if isinstance(rhs, (NumpyFull)):
23082321
return self.arrayFill(expr)
2309-
lhs = self._print(expr.lhs)
2322+
lhs_code = self._print(lhs)
23102323
if isinstance(rhs, (PythonList, PythonSet, PythonDict)):
23112324
return self.init_stc_container(rhs, expr)
2312-
rhs = self._print(expr.rhs)
2313-
return f'{lhs} = {rhs};\n'
2325+
rhs_code = self._print(rhs)
2326+
if isinstance(rhs, LiteralString):
2327+
rhs_code = f'cstr_lit({rhs_code})'
2328+
return f'{lhs_code} = {rhs_code};\n'
23142329

23152330
def _print_AliasAssign(self, expr):
23162331
lhs_var = expr.lhs

pyccel/codegen/python_wrapper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def create_shared_library(codegen,
198198
# Compile cwrapper_ndarrays from stdlib (if necessary)
199199
#--------------------------------------------------------
200200
start_compile_libs = time.time()
201-
manage_dependencies(wrapper_codegen, wrapper_compiler, pyccel_dirpath, wrapper_compile_obj,
201+
manage_dependencies(wrapper_codegen.get_additional_imports(), wrapper_compiler, pyccel_dirpath, wrapper_compile_obj,
202202
language, verbose)
203203
timings['Dependency compilation'] += (time.time() - start_compile_libs)
204204

0 commit comments

Comments
 (0)