Skip to content

Commit

Permalink
Split parsing from building
Browse files Browse the repository at this point in the history
  • Loading branch information
enekomartinmartinez committed Mar 2, 2022
1 parent 04fc997 commit 12238ce
Show file tree
Hide file tree
Showing 47 changed files with 5,363 additions and 335 deletions.
Empty file added pysd/building/__init__.py
Empty file.
Empty file.
78 changes: 78 additions & 0 deletions pysd/building/python/imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@

class ImportsManager():
"""
Class to save the imported modules information for intelligent import
"""
_external_libs = {"numpy": "np", "xarray": "xr"}
_external_submodules = ["scipy"]
_internal_libs = [
"functions", "statefuls", "external", "data", "lookups", "utils"
]

def __init__(self):
self._numpy, self._xarray, self._subs = False, False, False
self._functions, self._statefuls, self._external, self._data,\
self._lookups, self._utils, self._scipy =\
set(), set(), set(), set(), set(), set(), set()

def add(self, module, function=None):
"""
Add a function from module.
Parameters
----------
module: str
module name.
function: str or None
function name. If None module will be set to true.
"""
if function:
getattr(self, f"_{module}").add(function)
else:
setattr(self, f"_{module}", True)

def get_header(self, outfile):
"""
Returns the importing information to print in the model file
Parameters
----------
outfile: str
Name of the outfile to print in the header.
Returns
-------
text: str
Header of the translated model file.
"""
text =\
f'"""\nPython model \'{outfile}\'\nTranslated using PySD\n"""\n\n'

text += "from pathlib import Path\n"

for module, shortname in self._external_libs.items():
if getattr(self, f"_{module}"):
text += f"import {module} as {shortname}\n"

for module in self._external_submodules:
if getattr(self, f"_{module}"):
text += "%(module)s import %(submodules)s\n" % {
"module": module,
"submodules": ", ".join(getattr(self, f"_{module}"))}

text += "\n"

for module in self._internal_libs:
if getattr(self, f"_{module}"):
text += "from pysd.py_backend.%(module)s import %(methods)s\n"\
% {
"module": module,
"methods": ", ".join(getattr(self, f"_{module}"))}

if self._subs:
text += "from pysd import subs\n"

return text
145 changes: 145 additions & 0 deletions pysd/building/python/namespace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import re

from unicodedata import normalize

# used to create python safe names with the variable reserved_words
from keyword import kwlist
from builtins import __dir__ as bidir
from pysd.py_backend.components import __dir__ as cdir
from pysd.py_backend.data import __dir__ as ddir
from pysd.py_backend.decorators import __dir__ as dedir
from pysd.py_backend.external import __dir__ as edir
from pysd.py_backend.functions import __dir__ as fdir
from pysd.py_backend.statefuls import __dir__ as sdir
from pysd.py_backend.utils import __dir__ as udir


class NamespaceManager:
reserved_words = set(
dir() + bidir() + cdir() + ddir() + dedir() + edir() + fdir()
+ sdir() + udir()).union(kwlist)

def __init__(self, parameters=[]):
self.used_words = self.reserved_words.copy()
self.namespace = {"Time": "time"}
self.cleanspace = {"time": "time"}
for parameter in parameters:
self.add_to_namespace(parameter)

def add_to_namespace(self, string):
self.make_python_identifier(string, add_to_namespace=True)

def make_python_identifier(self, string, prefix=None, add_to_namespace=False):
"""
Takes an arbitrary string and creates a valid Python identifier.
If the input string is in the namespace, return its value.
If the python identifier created is already in the namespace,
but the input string is not (ie, two similar strings resolve to
the same python identifier)
or if the identifier is a reserved word in the reserved_words
list, or is a python default reserved word,
adds _1, or if _1 is in the namespace, _2, etc.
Parameters
----------
string: str
The text to be converted into a valid python identifier.
namespace: dict
Map of existing translations into python safe identifiers.
This is to ensure that two strings are not translated into
the same python identifier. If string is already in the namespace
its value will be returned. Otherwise, namespace will be mutated
adding string as a new key and its value.
Returns
-------
identifier: str
A vaild python identifier based on the input string.
Examples
--------
>>> make_python_identifier('Capital')
'capital'
>>> make_python_identifier('multiple words')
'multiple_words'
>>> make_python_identifier('multiple spaces')
'multiple_spaces'
When the name is a python keyword, add '_1' to differentiate it
>>> make_python_identifier('for')
'for_1'
Remove leading and trailing whitespace
>>> make_python_identifier(' whitespace ')
'whitespace'
Remove most special characters outright:
>>> make_python_identifier('H@t tr!ck')
'ht_trck'
add valid string to leading digits
>>> make_python_identifier('123abc')
'nvs_123abc'
already in namespace
>>> make_python_identifier('Var$', namespace={'Var$': 'var'})
'var'
namespace conflicts
>>> make_python_identifier('Var@', namespace={'Var$': 'var'})
'var_1'
>>> make_python_identifier('Var$', namespace={'Var@': 'var',
... 'Var%':'var_1'})
'var_2'
References
----------
Identifiers must follow the convention outlined here:
https://docs.python.org/2/reference/lexical_analysis.html#identifiers
"""
s = string.lower()
clean_s = s.replace(" ", "_")

if prefix is None and clean_s in self.cleanspace:
return self.cleanspace[clean_s]

# Make spaces into underscores
s = re.sub(r"[\s\t\n_]+", "_", s)

# remove accents, diaeresis and others ó -> o
s = normalize("NFD", s).encode("ascii", "ignore").decode("utf-8")

# Remove invalid characters
s = re.sub(r"[^0-9a-zA-Z_]", "", s)

# If leading character is not a letter add nvs_.
# Only letters can be leading characters.
if prefix is not None:
s = prefix + "_" + s
elif re.findall(r"^[0-9]", s):
s = "nvs_" + s
elif re.findall(r"^_", s):
s = "nvs" + s

# Check that the string is not a python identifier
identifier = s
i = 1
while identifier in self.used_words:
identifier = s + '_' + str(i)
i += 1

self.used_words.add(identifier)

if add_to_namespace:
self.namespace[string] = identifier
self.cleanspace[clean_s] = identifier

return identifier
Loading

0 comments on commit 12238ce

Please sign in to comment.