# coding=utf-8
# --------------------------------------------------------------------
# Copyright (C) 1991 - 2026 - EDF - www.code-aster.org
# This file is part of code_aster.
#
# code_aster is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# code_aster is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with code_aster. If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------
"""
:py:mod:`CommandSyntax` --- Interface between C++/Fortran operators and user syntax
***********************************************************************************
The :py:class:`CommandSyntax` provides an interface between operators originally
defined in Fortran (:file:`opXXXX` subroutines) and the user syntax.
The C/Fortran interface is defined in :file:`aster_module.c`.
It is also used in some C++ objects to emulate a user command.
The C++ interface is available from C++ ``CommandSyntax`` class.
The method :py:meth:`~CommandSyntax.define` stores the user keywords.
The execution of an operator (by :py:func:`libaster.call_oper` for example) registers
the current :py:class:`CommandSyntax` instance that can be requested by the
operator using the functions:
- :py:meth:`~CommandSyntax.getres`,
- :py:meth:`~CommandSyntax.getvtx`,
- :py:meth:`~CommandSyntax.getltx`,
- :py:meth:`~CommandSyntax.getvid`,
- :py:meth:`~CommandSyntax.getvis`,
- :py:meth:`~CommandSyntax.getvr8`,
- :py:meth:`~CommandSyntax.getvc8`,
- :py:meth:`~CommandSyntax.getfac`,
- :py:meth:`~CommandSyntax.getexm`,
- :py:meth:`~CommandSyntax.getmjm`.
"""
from ..Cata import Commands
from ..Cata.SyntaxChecker import CheckerError, SyntaxCheckerVisitor
from ..Objects import DataStructure
from ..Utilities import (
force_list,
import_object,
is_complex,
is_float,
is_int,
is_str,
logger,
value_is_sequence,
)
from ..Utilities.outputs import command_text
from .typeaster import typeaster
# WARNING:
# user keywords dict may be very big, uncomment 'logger.debug' lines
# only during debugging
[docs]class CommandSyntax:
"""This class describes the syntax of command for compatibility
with the fortran code that use the legacy supervisor.
"""
_currentCommand = None
[docs] @classmethod
def setCurrentCommand(cls, command):
"""Register current command.
Arguments:
command (*CommandSyntax*): *CommandSyntax* to register.
"""
# The object is registered with `register_sh_etape()` by `aster_oper`
# if called from Python or by the constructor of `CommandSyntax` if
# called from C++.
cls._currentCommand = command
[docs] @classmethod
def getCurrentCommand(cls):
"""Return the current command.
Returns:
*CommandSyntax*: Current registered *CommandSyntax*.
"""
return cls._currentCommand
[docs] def __init__(self, name="unNamed", cata=None):
"""Create a new command or part of syntax.
Arguments:
name (str): Command name (ex: "DEBUT") or path to catalog (ex:
"code_aster.Cata.Commands.DEBUT").
cata (*PartOfSyntax*): Command description.
"""
self._name = name.split(".")[-1]
self._resultName = " "
self._resultType = " "
self._resultValue = None
self._definition = None
logger.debug("Syntax: new command is %r", name)
currentCommand = self.getCurrentCommand()
# only FIN is allowed to free the current "in failure" command
if self._name == "FIN" and currentCommand is not None:
currentCommand.free()
currentCommand = self.getCurrentCommand()
assert currentCommand is None, "CommandSyntax {} must be freed".format(
currentCommand.getName()
)
self.setCurrentCommand(self)
if not cata:
if "." not in name:
cata = getattr(Commands, name, None)
else:
cata = import_object(name)
if not cata:
logger.debug("CommandSyntax: catalog not found for %r", name)
self._commandCata = cata
[docs] def free(self):
"""Reset the current command pointer as soon as possible"""
# `currentCommand` must be reset before the garbage collector will do it
# logger.debug("Syntax: del command %r", name)
self.setCurrentCommand(None)
[docs] def __repr__(self):
"""Representation of the command"""
return "Command {!r}, returns {!r} <{!r}>\n` syntax: {}".format(
self._name, self._resultName, self._resultType, self._definition
)
[docs] def debugPrint(self):
"""Representation of the command"""
logger.debug("%r", self)
[docs] def setResult(self, sdName, sdType):
"""Register the result of the command: name and type.
Arguments:
sdName (str): Name of the result created by the Command.
sdType (str): Type of the result created by the Command.
"""
self._resultName = sdName
self._resultType = sdType
[docs] def define(self, dictSyntax, add_default=True, check_syntax=False):
"""Register the keywords values.
Arguments:
dictSyntax (dict): User keywords.
add_default (bool, optional): Tell if default keywords have to be
added or not.
check_syntax (bool, optional): Check that the keywords validity.
Only when it has not already been checked by ExecuteCommand object.
"""
if self._commandCata is not None and add_default:
# logger.debug("define0 %r: %r", self._name, dictSyntax)
self._commandCata.addDefaultKeywords(dictSyntax)
if check_syntax:
checker = SyntaxCheckerVisitor()
try:
self._commandCata.accept(checker, dictSyntax)
except (CheckerError, KeyError, TypeError, ValueError) as exc:
text = command_text(self._name, dictSyntax)
logger.error("Invalid keywords:\n Error: %s\n Keywords:\n%s", exc, text)
self._definition = dictSyntax
# logger.debug("define1 %r: %r", self._name, self._definition)
[docs] def getName(self):
"""Return the command name.
Returns:
str: Command name.
"""
return self._name
[docs] def getResultName(self):
"""Return the name of the result of the Command.
Returns:
str: Name of the result of the Command.
"""
return self._resultName
[docs] def getResultType(self):
"""Return the type of the result of the command.
Returns:
str: Type name of the result of the Command.
"""
return self._resultType
[docs] def getResultValue(self):
"""Return the value of the result of the Command.
Returns:
str: Name of the result of the Command.
"""
return self._resultValue
[docs] def _getFactorKeyword(self, factName):
"""Return the occurrences of a factor keyword.
Arguments:
factName (str): Name of the factor keyword.
Returns:
list: List of occurences of a factor keyword, *None* if it is not
provided by the user.
"""
# a factor keyword may be empty: {} (None means 'does not exist')
dictDef = self._definition.get(factName, None)
# logger.debug("factor keyword %r: %r", factName, dictDef)
if dictDef is None:
return None
if isinstance(dictDef, dict):
dictDef = [dictDef]
return dictDef
[docs] def _getFactorKeywordOccurrence(self, factName, occurrence):
"""Return the definition of an occurrence of a factor keyword.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
Returns:
dict: Occurrence of a factor keyword, *None* if it is not
provided by the user."""
dictDef = self._getFactorKeyword(factName)
if dictDef is None:
return None
try:
return dictDef[occurrence]
except Exception:
return None
[docs] def _getDefinition(self, factName, occurrence):
"""Return the definition of a factor keyword or of the top-level
if `factName` is blank.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
Returns:
dict: User keywords under a factor keyword or the Command.
"""
if not factName.strip():
dictDef = self._definition
else:
dictDef = self._getFactorKeywordOccurrence(factName, occurrence)
if not dictDef:
dictDef = {}
assert isinstance(dictDef, dict), "syntax not defined"
return dictDef
[docs] def _getCataDefinition(self, factName):
"""Return the definition of a factor keyword in the catalog or of the
top-level if `factName` is blank.
Arguments:
factName (str): Name of the factor keyword.
Returns:
*CataDefinition*: Definition of the factor keyword or the Command.
"""
if not self._commandCata:
logger.debug("CommandSyntax: catalog is not available")
return None
factName = factName.strip()
catadef = self._commandCata.definition
if not factName:
return catadef
keywords = catadef.factor_keywords.get(factName)
if not keywords:
return None
return keywords.definition
[docs] def getFactorKeywordNbOcc(self, factName):
"""Return the number of occurrences of a factor keyword in the
user's keywords.
Arguments:
factName (str): Name of the factor keyword.
Returns:
int: Number of occurrences of a factor keyword.
"""
dictDef = self._getFactorKeyword(factName)
# logger.debug("_getFactorKeyword %r: %r", factName, dictDef)
if dictDef is None:
return 0
# logger.debug("getFactorKeywordNbOcc: len(dictDef) = %d", len(dictDef))
return len(dictDef)
getfac = getFactorKeywordNbOcc
[docs] def existsFactorAndSimpleKeyword(self, factName, occurrence, simpName):
"""Tell if the couple ( factor keyword, simple keyword ) exists in the
user keywords.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
simpName (str): Name of the simple keyword.
Returns:
int: 1 if the keyword exists, else 0.
"""
dictDef = self._getDefinition(factName, occurrence)
value = dictDef.get(simpName, None)
if value is None or isinstance(value, dict):
return 0
return 1
[docs] def getValue(self, factName, occurrence, simpName):
"""Return the values of a (simple) keyword.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
simpName (str): Name of the simple keyword.
Returns:
list[misc]: List of the values provided by the user.
"""
if not self.existsFactorAndSimpleKeyword(factName, occurrence, simpName):
return []
value = self._getDefinition(factName, occurrence)[simpName]
value = force_list(value)
# logger.debug("getValue: %s %s %s", factName, simpName, value)
return value
[docs] def getltx(self, factName, simpName, occurrence, maxval, lenmax):
"""Wrapper function to return length of strings.
Arguments:
factName (str): Name of the factor keyword.
simpName (str): Name of the simple keyword.
occurrence (int): Index of the occurrence (start from 0).
maxval (int): Maximum number of values read.
lenmax (int): Maximum of lengths.
Returns:
list[misc]: List of the values provided by the user.
"""
value = self.getValue(factName, occurrence, simpName)
value = _check_strings(factName, simpName, value)
size = len(value)
if size > maxval:
size = -size
length = [min(len(i), lenmax) for i in value]
return size, tuple(length[:maxval])
[docs] def getvid(self, factName, simpName, occurrence, maxval):
"""Wrapper function to return a list of results.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
simpName (str): Name of the simple keyword.
maxval (int): Maximum number of values read.
Returns:
int, list: Returns two values ``(size, values)``.
``size`` is the number of the values provided by the user.
If ``size > maxval``, ``-size`` is returned.
``values`` is a list of result names.
"""
value = self.getValue(factName, occurrence, simpName)
value = [i.getName() if hasattr(i, "getName") else i for i in value]
size = len(value)
if size > maxval:
size = -size
return size, tuple(value[:maxval])
[docs] def getvtx(self, factName, simpName, occurrence, maxval):
"""Wrapper function to return a list of strings.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
simpName (str): Name of the simple keyword.
maxval (int): Maximum number of values read.
Returns:
int, list: Returns two values ``(size, values)``.
``size`` is the number of the values provided by the user.
If ``size > maxval``, ``-size`` is returned.
``values`` is a list of strings.
"""
value = self.getValue(factName, occurrence, simpName)
value = _check_strings(factName, simpName, value)
size = len(value)
if size > maxval:
size = -size
return size, tuple(value[:maxval])
[docs] def getvis(self, factName, simpName, occurrence, maxval):
"""Wrapper function to return a list of integers.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
simpName (str): Name of the simple keyword.
maxval (int): Maximum number of values read.
Returns:
int, list: Returns two values ``(size, values)``.
``size`` is the number of the values provided by the user.
If ``size > maxval``, ``-size`` is returned.
``values`` is a list of integers.
"""
value = self.getValue(factName, occurrence, simpName)
if len(value) > 0 and not is_int(value[0], onvalue=True):
raise TypeError("integer expected, got %s" % type(value[0]))
size = len(value)
if size > maxval:
size = -size
return size, tuple([round(i) for i in value[:maxval]])
[docs] def getvr8(self, factName, simpName, occurrence, maxval):
"""Wrapper function to return a list of float numbers.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
simpName (str): Name of the simple keyword.
maxval (int): Maximum number of values read.
Returns:
int, list: Returns two values ``(size, values)``.
``size`` is the number of the values provided by the user.
If ``size > maxval``, ``-size`` is returned.
``values`` is a list of floats.
"""
value = self.getValue(factName, occurrence, simpName)
if len(value) > 0:
try:
float(value[0])
except TypeError:
raise TypeError("float expected, got %s" % type(value[0]))
size = len(value)
if size > maxval:
size = -size
return size, tuple(value[:maxval])
[docs] def getvc8(self, factName, simpName, occurrence, maxval):
"""Wrapper function to return a list of complex numbers.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
simpName (str): Name of the simple keyword.
maxval (int): Maximum number of values read.
Returns:
int, list: Returns two values ``(size, values)``.
``size`` is the number of the values provided by the user.
If ``size > maxval``, ``-size`` is returned.
``values`` is a list of complex numbers.
"""
values = self.getValue(factName, occurrence, simpName)
if len(values) > 0:
if isinstance(values[0], str):
values = (values,)
toReturn = []
for value in values:
if type(value) in (list, tuple):
assert value[0] in ("RI", "MP")
toReturn.append(tuple(value))
else:
toReturn.append(value)
size = len(toReturn)
if size > maxval:
size = -size
return size, tuple(toReturn[:maxval])
size = len(values)
if size > maxval:
size = -size
return size, tuple(values[:maxval])
[docs] def getvpy(self, factName, simpName, occurrence):
"""Wrapper function to return a list of Python objects.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
simpName (str): Name of the simple keyword.
Returns:
list: Tuple containing the objects provided by the user.
"""
try:
values = tuple(self.getValue(factName, occurrence, simpName))
except Exception as exc:
print("DEBUG: exception:", str(exc))
values = ()
return len(values), values
[docs] def getres(self):
"""Return the name and type of the result, and the command name.
Returns:
str: Name of the Command result (name of the jeveux object).
str: Type name of the result.
str: Command name.
"""
jev, typ, cmd = self.getResultName(), self.getResultType(), self.getName()
# logger.debug("Command %s: result name %r, type %r", cmd, jev, typ)
return jev, typ, cmd
[docs] def setres(self, value):
"""Define a value for special commands that returns a builtin type
(*int* or *float*).
Arguments:
value (int|float): Value returned.
"""
self._resultValue = value
[docs] def getexm(self, factName, simpName):
"""Tell if the couple ( factor keyword, simple keyword ) exists in the
Command catalog. It supposes that all blocs conditions are verified.
Arguments:
factName (str): Name of the factor keyword.
simpName (str): Name of the simple keyword.
Returns:
int: 1 if the keyword exists, else 0.
"""
catadef = self._getCataDefinition(factName)
# logger.debug("getexm: %s / %s", factName, simpName)
if not catadef:
return 0
if not simpName.strip():
return 1
keywords = catadef.simple_keywords
# logger.debug("getexm: simple keywords: %s", list(keywords.keys()))
return int(keywords.get(simpName) is not None)
[docs] def getmjm(self, factName, occurrence, maxval):
"""Return the list of simple keywords provided by the user under a
factor keyword.
Arguments:
factName (str): Name of the factor keyword.
occurrence (int): Index of the occurrence (start from 0).
maxval (int): Maximum number of values returned.
Returns:
list[str], list[str]: Two lists: one for the names of simple
keywords, alphabetically sorted, and one for the type names
of the keywords.
"""
userkw = self._getDefinition(factName, occurrence)
if not userkw:
return (), ()
# logger.debug("getmjm: user keywords: %s", userkw)
catadef = self._getCataDefinition(factName).simple_keywords
lkeywords = sorted(userkw.keys())
kws, types = [], []
for kw in lkeywords:
obj = userkw[kw]
if obj is None:
continue
# ignore factor keyword: legacy getmjm returned typ='MCList'
if kw not in catadef:
continue
kws.append(kw)
typ = typeaster(catadef[kw].definition["typ"])
if value_is_sequence(obj) and not is_complex(obj):
obj = obj[0]
if is_complex(obj) and typ == "C8":
pass
elif is_float(obj) and typ in ("R8", "C8"):
pass
elif is_int(obj, onvalue=True) and typ in ("IS", "R8", "C8"):
pass
elif is_str(obj) and typ == "TX":
pass
elif is_str(obj) and "FORMULE" in typ:
typ = typ[0]
elif isinstance(obj, DataStructure):
typ = obj.getType()
else:
raise TypeError("unsupported type: {0!r} {1}".format(obj, type(obj)))
types.append(typ)
return kws, types
def getmat(self):
raise NotImplementedError("'getmat' is not yet supported.")
[docs] def getoper(self):
"""Return the operator number.
Returns:
int: Number of the fortran operator subroutine.
"""
return self._commandCata.definition["op"]
[docs]def _check_strings(factName, simpName, value):
"""Check that keyword values are strings.
Arguments:
value (str): Values extracted from a keyword.
Returns:
list[str]: String values or names for DataStructure objects.
"""
if len(value) > 0 and not is_str(value[0]):
try:
value2 = []
for i in range(len(value)):
value2.append(value[i].getName())
value = value2
except AttributeError:
raise TypeError(
"string expected for {0}/{1}, got {2}".format(factName, simpName, type(value[0]))
)
return value