Skip to content

Commit

Permalink
refactored to enable pyx
Browse files Browse the repository at this point in the history
  • Loading branch information
nucccc committed Jun 5, 2024
1 parent 92a5038 commit dd5015b
Show file tree
Hide file tree
Showing 13 changed files with 471 additions and 279 deletions.
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) -> 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
201 changes: 201 additions & 0 deletions markarth/convert/cythonize/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
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
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
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

0 comments on commit dd5015b

Please sign in to comment.