From 018ea745a9a720c9e13b7641764a35dcbed306e0 Mon Sep 17 00:00:00 2001 From: Bill Hlavacek Date: Sun, 10 May 2026 15:32:18 -0600 Subject: [PATCH 1/2] Decode `&&` / `||` boolean operators when re-emitting from BNG-XML MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BNG2.pl's serializer rewrites the binary operators ``&&`` and ``||`` as the function-call-shaped strings ``(L)and(R)`` and ``(L)or(R)`` (see Perl2/Expression.pm: '&&' => 'and'). Reading verbatim and re-emitting it into a regenerated .bngl file confuses BNG2.pl's BNGL parser, which sees ``and(...)`` as a function call and aborts with "Missing end parentheses ... at and(...)". Fix: in xmlparsers, run a regex pass that maps ``)and(`` → ``) && (`` and ``)or(`` → ``) || (`` before storing the function expression. The match is anchored on the close-paren of the left operand because BNG2.pl always wraps both operands in parens for these forms — so ``)and(`` / ``)or(`` only ever appear as the boolean-operator encoding and never as the suffix of a user identifier or a literal function call. --- bionetgen/modelapi/xmlparsers.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/bionetgen/modelapi/xmlparsers.py b/bionetgen/modelapi/xmlparsers.py index bf529821..0b0abe09 100644 --- a/bionetgen/modelapi/xmlparsers.py +++ b/bionetgen/modelapi/xmlparsers.py @@ -1,3 +1,5 @@ +import re + from .blocks import ParameterBlock, CompartmentBlock, ObservableBlock from .blocks import SpeciesBlock, MoleculeTypeBlock from .blocks import FunctionBlock, RuleBlock @@ -7,6 +9,28 @@ from .rulemod import RuleMod +# BNG2.pl serializes the boolean operators ``&&`` and ``||`` in +# elements as the function-call-shaped strings ``(operand)and(operand)`` and +# ``(operand)or(operand)`` (see Perl2/Expression.pm: '&&' => 'and'). When we +# re-emit that body into a .bngl file BNG2.pl re-parses ``and(...)`` as a +# function call and aborts ("Missing end parentheses ... at and(...)"), so +# we have to undo the substitution here. The match is intentionally +# anchored on the closing-paren of the left operand: BNG2.pl always wraps +# both operands in parens when emitting these forms, so ``)and(`` / +# ``)or(`` only appear as the boolean-operator encoding — never as the +# tail of a user-defined identifier or a literal function call. +_AND_OP_RE = re.compile(r"\)and\(") +_OR_OP_RE = re.compile(r"\)or\(") + + +def _decode_xml_boolean_ops(expr): + """Translate BNG2.pl's ``)and(`` / ``)or(`` XML encoding back to ``&&`` / ``||``.""" + if not expr: + return expr + expr = _AND_OP_RE.sub(") && (", expr) + expr = _OR_OP_RE.sub(") || (", expr) + return expr + ###### Base object ###### class XMLObj: @@ -494,7 +518,7 @@ def parse_xml(self, xml): for f in xml: # add content to line fname = f["@id"] - expr = f["Expression"] + expr = _decode_xml_boolean_ops(f["Expression"]) args = [] if "ListOfArguments" in f: args = self.get_arguments(f["ListOfArguments"]["Argument"]) @@ -502,7 +526,7 @@ def parse_xml(self, xml): block.add_function(fname, expr, args=args) else: fname = xml["@id"] - expr = xml["Expression"] + expr = _decode_xml_boolean_ops(xml["Expression"]) args = [] if "ListOfArguments" in xml: args = self.get_arguments(xml["ListOfArguments"]["Argument"]) From fa4d1f3d73736566d0eb80b731a784006389fb82 Mon Sep 17 00:00:00 2001 From: jrfaeder Date: Mon, 11 May 2026 12:09:50 -0400 Subject: [PATCH 2/2] Trigger GitHub PR status refresh