@@ -1161,11 +1161,11 @@ def is_reference_rule(self):
11611161 """
11621162 if self .rule :
11631163 if isinstance (self .rule , str ):
1164- if self .rule [ 0 ] == "[" :
1164+ if self .rule . startswith ( "[" ) and self . rule . endswith ( "]" ) :
11651165 return True
11661166 else :
11671167 assert isinstance (self .rule , bytes )
1168- if self .rule [ 0 ] == b"[" :
1168+ if self .rule . startswith ( b"[" ) and self . rule . endswith ( b"]" ) :
11691169 return True
11701170 return False
11711171
@@ -1187,7 +1187,7 @@ def full_type(self):
11871187
11881188 def get_rule (self ):
11891189 """Gets `Rule` from a specified variable"""
1190- return Rule (name = self .rule )
1190+ return Rule (verbatim = self .rule , is_reference = self . is_reference_rule () )
11911191
11921192 def set_rule (self , rule ):
11931193 """Sets a rule on a specified variable in the dictionary
@@ -1383,7 +1383,7 @@ def get_value(self, key):
13831383
13841384 def get_rule (self ):
13851385 """Gets `Rule` from a specified variable block"""
1386- return Rule (name = self .rule )
1386+ return Rule (verbatim = self .rule )
13871387
13881388 def set_rule (self , rule ):
13891389 """Sets a rule on a specified variable block in the dictionary
@@ -1397,9 +1397,14 @@ def set_rule(self, rule):
13971397 ------
13981398 `TypeError`
13991399 If ``rule`` is not of type `Rule`
1400+
1401+ `ValueError`
1402+ If ``rule`` is a reference rule
14001403 """
14011404 if not isinstance (rule , Rule ):
14021405 raise TypeError (type_error_message ("rule" , rule , Rule ))
1406+ if rule .is_reference :
1407+ raise ValueError ("Cannot set reference rule on a variable block" )
14031408 self .rule = repr (rule )
14041409
14051410 def write (self , writer ):
@@ -1455,20 +1460,12 @@ def write(self, writer):
14551460
14561461
14571462class Rule :
1458- """A rule of a variable in a Khiops dictionary
1463+ """A rule of a variable or variable block in a Khiops dictionary
14591464
14601465 Parameters
14611466 ----------
1462- name : str or bytes
1463- Name or verbatim of the rule. It is intepreted as the verbatim
1464- representation of an entire rule if and only if:
1465-
1466- - it starts with an UpperCamelCase string, followed by a
1467- parenthesized block (...)
1468- - ``operands`` is empty
1469-
1470- operands : tuple of operands
1471- Each operand can have one of the following types:
1467+ name_and_operands : tuple
1468+ Each tuple member can have one of the following types:
14721469
14731470 - str
14741471 - bytes
@@ -1479,15 +1476,24 @@ class Rule:
14791476 - upper-scoped `Variable`
14801477 - upper-scoped `Rule`
14811478
1479+ .. note::
1480+ The first element of the ``name_and_operands`` tuple is the name of
1481+ the rule and must be str or bytes and non-empty for a standard rule,
1482+ i.e. if ``is_reference`` is not set.
1483+
1484+ verbatim : str or bytes, optional
1485+ Verbatim representation of an entire rule. If set, then ``names_and_operands``
1486+ must be empty.
1487+
14821488 is_reference : bool, default ``False``
14831489 If set to ``True``, then the rule is serialized as a reference rule:
14841490 ``Rule(Operand1, Operand2, ...)`` is serialized as
14851491 ``[Operand1, Operand2, ...]``.
14861492
14871493 Attributes
14881494 ----------
1489- name : str or bytes
1490- Name of the rule.
1495+ name : str or bytes or ``None``
1496+ Name of the rule. It is ``None`` for reference rules.
14911497 operands : tuple of operands
14921498 Each operand has one of the following types:
14931499
@@ -1507,12 +1513,42 @@ class Rule:
15071513 This attribute cannot be changed on a `Rule` instance.
15081514 """
15091515
1510- def __init__ (self , name , * operands , is_reference = False ):
1516+ def __init__ (self , * name_and_operands , verbatim = None , is_reference = False ):
15111517 """See class docstring"""
1512- # Check input parameters
1513- if not is_string_like (name ):
1514- raise TypeError (type_error_message ("name" , name , "string-like" ))
1515- for operand in operands :
1518+ # Check input parameters and initialize rule fragments accordigly
1519+ if not isinstance (is_reference , bool ):
1520+ raise TypeError (type_error_message ("is_reference" , is_reference , bool ))
1521+
1522+ # Rule provided as name plus operands
1523+ if verbatim is None :
1524+ if not name_and_operands :
1525+ raise ValueError ("A name must be provided to a standard rule" )
1526+ if is_reference :
1527+ self .name = None
1528+ self .operands = name_and_operands
1529+ else :
1530+ name , * operands = name_and_operands
1531+ if not is_string_like (name ):
1532+ raise TypeError (type_error_message ("name" , name , "string-like" ))
1533+ if not name :
1534+ raise ValueError ("'name' must be a non-empty string" )
1535+ self .name = name
1536+ self .operands = operands
1537+ # Rule provided as verbatim
1538+ else :
1539+ if not is_string_like (verbatim ):
1540+ raise TypeError (type_error_message ("verbatim" , verbatim , "string-like" ))
1541+ if not verbatim :
1542+ raise ValueError ("'verbatim' must be a non-empty string" )
1543+ if name_and_operands :
1544+ raise ValueError (
1545+ "Rule name and operands must not be provided for verbatim rules"
1546+ )
1547+ self .name = None
1548+ self .operands = ()
1549+
1550+ # Check operand types
1551+ for operand in self .operands :
15161552 if not is_string_like (operand ) and not isinstance (
15171553 operand , (int , float , Variable , Rule , _ScopedOperand )
15181554 ):
@@ -1529,14 +1565,9 @@ def __init__(self, name, *operands, is_reference=False):
15291565 "upper-scoped Rule" ,
15301566 )
15311567 )
1532- if not isinstance (is_reference , bool ):
1533- raise TypeError (type_error_message ("is_reference" , is_reference , bool ))
1534- if not is_reference and not name :
1535- raise ValueError ("'name' must be a non-empty string" )
15361568
1537- # Initialize attributes
1538- self .name = name
1539- self .operands = operands
1569+ # Initialize private attributes
1570+ self ._verbatim = verbatim
15401571 self ._is_reference = is_reference
15411572
15421573 @property
@@ -1568,8 +1599,7 @@ def write(self, writer):
15681599 Output writer.
15691600
15701601 .. note::
1571- If ``self.is_reference`` is set, then ``self.name`` is not
1572- included in the serialization.
1602+ ``self.name`` is not included in the serialization of reference rules.
15731603 """
15741604 # Check the type of the writer
15751605 if not isinstance (writer , KhiopsOutputWriter ):
@@ -1606,14 +1636,17 @@ def write(self, writer):
16061636 writer .write ("]" )
16071637 else :
16081638 writer .write (")" )
1609- # Write verbatim-given rule
1639+ # Write no-operand rule
16101640 elif (
16111641 isinstance (self .name , str )
16121642 and rule_regex .match (self .name )
16131643 or isinstance (self .name , bytes )
16141644 and bytes_rule_regex .match (self .name )
16151645 ):
16161646 writer .write (self .name )
1647+ # Write verbatim-given rule
1648+ elif self ._verbatim :
1649+ writer .write (self ._verbatim )
16171650
16181651
16191652class _ScopedOperand :
0 commit comments