Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/pyx converter #2

Merged
merged 2 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions markarth/convert/convert_pure.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from markarth.convert.preprocess import code_process
from markarth.convert.collect.mod_collect import mod_collect
from markarth.convert.cythonize.cy_options import ModOpts, gen_default_mod_opts
from markarth.convert.cythonize.pure import pure_cythonize
from markarth.convert.cythonize.pure import cythonify_pure


def convert_code(
Expand All @@ -19,7 +19,7 @@ def convert_code(

mod_collect_res = mod_collect(ast_mod, m_opts)

new_code = pure_cythonize(
new_code = cythonify_pure(
mod_ast = ast_mod,
codelines = codelines,
mod_coll = mod_collect_res,
Expand Down
4 changes: 4 additions & 0 deletions markarth/convert/cythonize/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#from .pure import cythonify_pure
#from .pyx import cythonify_pyx

#from .cy_options import ModOpts, gen_default_mod_opts
57 changes: 57 additions & 0 deletions markarth/convert/cythonize/_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'''
_import shall be a library with functions to check if cython is actually
imported, and eventually import it
'''

import ast


CYTHON_MODULE_NAME = 'cython'


def is_import_cython(imp_stat : ast.Import) -> tuple[bool, str]:
'''
is_import_cython receives an import ast statement in input, and
returns a tuple with:

- a boolean, True if cython is actually imported
- a str
'''
for al in imp_stat.names:
if al.name == CYTHON_MODULE_NAME:
asname = al.asname
alias_name = asname if asname else CYTHON_MODULE_NAME
return (True, alias_name)
return (False, '')


def cython_imported_already(mod_ast : ast.Module) -> tuple[bool, str, int]:
'''
this should somehow return true if cython is already installed or not
'''
for stat in mod_ast.body:
if type(stat) == ast.Import:
check_result = is_import_cython(stat)
if check_result[0]:
return (check_result[0], check_result[1], stat.lineno)
return (False, '', 0)


def gen_import_line(cy_alias : str | None = None) -> str:
'''
gen_import_line generates an import cython line
'''
if cy_alias is None or cy_alias == CYTHON_MODULE_NAME:
return f'import {CYTHON_MODULE_NAME}'
return f'import {CYTHON_MODULE_NAME} as {cy_alias}'

def add_cython_import(
codelines : list[str],
cy_alias : str | None
) -> list[str]:
'''
add_cython_import imports at the first line a codeline importing cython
'''
imp_line = gen_import_line(cy_alias)
codelines.insert(1, imp_line)
return codelines
203 changes: 203 additions & 0 deletions markarth/convert/cythonize/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import ast

from dataclasses import dataclass
from typing import Callable, Iterator


from markarth.convert.collect.func_collect import LocalCollectionResult
from markarth.convert.collect.mod_collect import ModCollectionResult
from markarth.convert.cythonize._import import (
add_cython_import,
cython_imported_already
)
from markarth.convert.cythonize.cy_options import FuncOpts, ModOpts
from markarth.convert.cythonize.cy_typs import CyFloat, CyInt
from markarth.convert.preprocess.code_process import indentation_pattern
from markarth.convert.typs.typ_store import TypStore


DEFAULT_CY_ALIAS = 'cython'

# defining a type for a callable that generates a type declare line
typDeclGen = Callable[[str, str, str, str], str]

typStoreConvFunc = Callable[
[TypStore, CyInt, CyFloat, dict[str, CyInt | CyFloat], str, str],
Iterator[str]
]


@dataclass
class CythonifyLogic:
'''
CythonifyLogic shall be a collection to be used to characterize
how a specific conversion shall be done
'''
cython_import_needed : bool
typ_store_to_cdeclares : typStoreConvFunc


def cythonify(
mod_ast : ast.Module,
codelines : list[str],
mod_coll : ModCollectionResult,
m_opts : ModOpts,
clogic : CythonifyLogic
) -> str:
'''
yep maybe this should return a portion of code with cythonized stuff, in pure
python mode for cython 3.0
'''
# declaring varibles to be used during conversion
func_asts : dict[str, ast.FunctionDef] = mod_coll.func_asts
funcs_collected : dict[str, LocalCollectionResult] = mod_coll.func_colls
consts_typ_store : TypStore = mod_coll.global_typs

# at first i shall check if cython is imported, and in such case i consider
# its alias
alias = '' # TODO: remove this line once you're back pyx tests
if clogic.cython_import_needed:

is_cython_imported, alias, codeline_no = cython_imported_already(mod_ast)

if not is_cython_imported:
alias = DEFAULT_CY_ALIAS
# in case the conversion logic does not require me to have a cython import,
# the alias variable is however set to be passed as a parameter to other functions
# TODO: decomment below once you have tests for pyx
# else:
# alias = ''

# function names sorted in order of appearance, as it's more practical to modify
# them from the last to the first, so that last functions to be modified won't have
# codeline indexes corrupted
func_names_sorted : list[str] = sort_funcs_by_line(func_asts)

for func_name in reversed(func_names_sorted):
func_ast = func_asts[func_name]
collect_result = funcs_collected[func_name]
func_opts = m_opts.get_f_opt_or_default(func_name)
codelines = add_c_lines(
func_ast = func_ast,
codelines = codelines,
collect_result = collect_result,
func_opts = func_opts,
typ_store_to_cdeclares = clogic.typ_store_to_cdeclares,
cy_alias = alias
)

codelines = add_global_c_lines(
codelines = codelines,
gloabal_typs = consts_typ_store,
m_opts = m_opts,
typ_store_to_cdeclares = clogic.typ_store_to_cdeclares,
cy_alias = alias
)

if clogic.cython_import_needed:
codelines = add_cython_import(codelines = codelines, cy_alias = alias)

return '\n'.join(codelines)


def add_c_lines(
func_ast : ast.FunctionDef,
codelines : list[str],
collect_result : LocalCollectionResult,
func_opts : FuncOpts,
typ_store_to_cdeclares : typStoreConvFunc,
cy_alias : str = DEFAULT_CY_ALIAS
) -> list[str]:
'''
add_c_lines modifies and returns codelines by adding the cdeclare lines
for the types of the function's local variables
'''
ins_point : int = cdeclares_ins_point(func_ast)
indent_pattern = indentation_pattern(func_ast, codelines)
cdeclares = typ_store_to_cdeclares(
typ_store = collect_result.local_typs,
default_cy_int = func_opts.default_int_cytyp,
default_cy_float = func_opts.default_float_cytyp,
imposed_vars = func_opts.imposed_vars,
cy_alias = cy_alias,
indent_pattern = indent_pattern
)
for cdeclare in cdeclares:
codelines.insert(ins_point, cdeclare)
return codelines


def add_global_c_lines(
codelines : list[str],
gloabal_typs : TypStore,
m_opts : ModOpts,
typ_store_to_cdeclares : typStoreConvFunc,
cy_alias : str = DEFAULT_CY_ALIAS
) -> list[str]:
'''
add_global_c_lines adds at the beginning of the script the cdeclares for
global variables
'''
cdeclares = typ_store_to_cdeclares(
typ_store = gloabal_typs,
default_cy_int = m_opts.default_int_cytyp,
default_cy_float = m_opts.default_float_cytyp,
imposed_vars = m_opts.imposed_consts,
cy_alias = cy_alias,
indent_pattern = ''
)
for cdeclare in cdeclares:
codelines.insert(1, cdeclare)
return codelines


@dataclass
class CyVarType:
'''
CyVarType shall just be a pair linking to each variable name a cython type
'''
varname : str
cy_type : str


def could_be_docstring(stat : ast.AST) -> bool:
'''
could_be_docstring returns True if an ast statement could be seen as a
docstring
'''
if not type(stat) is ast.Expr:
return False
#if not hasattr(stat, 'value'):
# return False
if not hasattr(stat.value, 'n'):
return False
return type(stat.value.n) is str


def cdeclares_ins_point(func_ast : ast.FunctionDef) -> int:
'''
cdeclares_ins_point shall return me the
'''
# i loop through all the statements and return the line number of the first
# one not being a docstring
for stat in func_ast.body:
if not could_be_docstring(stat):
return stat.lineno
# by default i return the first line
return func_ast.body[0].lineno


def sort_funcs_by_line(func_asts : dict[str, ast.FunctionDef]) -> list[str]:
'''
sort_funcs_by_line returns a list with the names of the functions sorted
by the order they appear in the code
'''
# TODO: the performance of this code could be optimized
func_names_by_line : dict[int, str] = {
cdeclares_ins_point(func_ast) : func_name
for func_name, func_ast in func_asts.items()
}
func_lines : list[int] = list(func_names_by_line)
func_lines.sort()

return [func_names_by_line[lineno] for lineno in func_lines]
2 changes: 2 additions & 0 deletions markarth/convert/cythonize/cy_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from typing import Dict

# TODO: a kinda type for imposed vars

# yeah fields may be all optional, with default stuff and so on
class FuncOpts(BaseModel):
internal_default_int_cytyp : CyInt | None = Field(default = None, description='default c type to be used for integers')
Expand Down
Loading
Loading