From e12769d0ffbe9a606513928fbf8326c33f03cde2 Mon Sep 17 00:00:00 2001 From: "Frank T. Bergmann" Date: Wed, 21 Aug 2024 16:54:30 +0200 Subject: [PATCH 1/8] - add libsbml converter to gui / cli --- moccasin/interfaces/controller.py | 8 ++++ moccasin/interfaces/moccasin_CLI.py | 27 +++++++++--- moccasin/interfaces/moccasin_GUI.py | 66 +++++++++++++++++++---------- 3 files changed, 72 insertions(+), 29 deletions(-) diff --git a/moccasin/interfaces/controller.py b/moccasin/interfaces/controller.py index a646778..abf71df 100644 --- a/moccasin/interfaces/controller.py +++ b/moccasin/interfaces/controller.py @@ -89,6 +89,14 @@ def build_model(self, use_species, output_format, name_after_param, add_comments add_comments) return output + def build_reaction_model_libsbml(self, use_species, name_after_param, add_comments): + '''Converts a parsed file into reaction-based SBML.''' + + (output, _, _) = create_raterule_model(self.parse_results, use_species, + 'sbml', name_after_param, + add_comments, convert_to_reactions=True) + return output + def build_reaction_model(self, use_species, name_after_param, add_comments): '''Converts a parsed file into reaction-based SBML.''' diff --git a/moccasin/interfaces/moccasin_CLI.py b/moccasin/interfaces/moccasin_CLI.py index 6a1755c..71b5ac0 100755 --- a/moccasin/interfaces/moccasin_CLI.py +++ b/moccasin/interfaces/moccasin_CLI.py @@ -61,11 +61,12 @@ debug_parser = ('print debug information about the parsed MATLAB', 'flag', 'D'), version = ('print MOCCASIN version info and exit', 'flag', 'V'), no_comments = ('do not insert version comments into SBML output', 'flag', 'X'), + use_libsbml = ('use libsbml converter for reaction-based SBML (default=False)', 'flag', 'l'), paths = 'paths to MATLAB input files to convert' ) def cli_main(gui, use_equations, use_params, quiet, relaxed, xpp_output, - no_color, debug_parser, version, no_comments, *paths): + no_color, debug_parser, version, no_comments, use_libsbml=False, *paths): '''Interface for controlling MOCCASIN, the MATLAB ODE converter for SBML. MOCCASIN can take certain forms of ODE (ordinary differential equation) models written in MATLAB and Octave and export them as SBML files. MOCCASIN does not @@ -128,6 +129,9 @@ def cli_main(gui, use_equations, use_params, quiet, relaxed, xpp_output, -e (/e on Windows) makes the translator create equation-based SBML output, instead of converting the model to reaction-based form (the default) + -l (/l on Windows) makes the translator use libsbml for the reaction-based + SBML output, instead of biocham + -p (/p on windows) makes the translator encode variables as SBML parameters, instead of the default, which is to use SBML species @@ -153,7 +157,7 @@ def cli_main(gui, use_equations, use_params, quiet, relaxed, xpp_output, add_comments = not no_comments if gui or not any([paths, use_equations, use_params, xpp_output, - version, debug_parser, quiet, no_comments]): + version, debug_parser, quiet, no_comments, use_libsbml]): moccasin_GUI.gui_main() sys.exit() if version: @@ -166,7 +170,7 @@ def cli_main(gui, use_equations, use_params, quiet, relaxed, xpp_output, sys.exit() if not paths: raise SystemExit(color('Must provide a path to a file.', 'error', colorize)) - if not xpp_output and not use_equations and not have_network(): + if not xpp_output and not use_equations and not use_libsbml and not have_network(): raise SystemExit(color('No network connection.', 'error', colorize)) if not quiet: from halo import Halo @@ -204,10 +208,16 @@ def convert(path): name_after_param = False, add_comments = add_comments) else: - text = 'Reaction-based SBML output' - output = controller.build_reaction_model(use_species = (not use_params), - name_after_param = False, - add_comments = add_comments) + if use_libsbml: + text = 'Reaction-based SBML output (libsbml)' + output = controller.build_reaction_model_libsbml(use_species = (not use_params), + name_after_param = False, + add_comments = add_comments) + else: + text = 'Reaction-based SBML output (biocham)' + output = controller.build_reaction_model(use_species = (not use_params), + name_after_param = False, + add_comments = add_comments) if debug_parser: print_header(text, 'info', quiet, colorize) msg(output) @@ -222,6 +232,9 @@ def convert(path): 'warning', colorize) msg('Renaming to "{}".'.format(backup_path), 'warning', colorize) + # remove backup if it exists (otherwise rename will fail) + if os.path.exists(backup_path): + os.remove(backup_path) os.rename(output_path, backup_path) with open(output_path, 'w') as output_file: output_file.write(output) diff --git a/moccasin/interfaces/moccasin_GUI.py b/moccasin/interfaces/moccasin_GUI.py index b8fb11f..1fe2907 100755 --- a/moccasin/interfaces/moccasin_GUI.py +++ b/moccasin/interfaces/moccasin_GUI.py @@ -303,7 +303,7 @@ def __init__(self, parent): sbSizer9.Add(gSizer7, 0, wx.EXPAND, 5) optionInset = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "Conversion options"), wx.VERTICAL) - optionGrid = wx.GridSizer(1, 2, 10, 10) + optionGrid = wx.GridSizer(1, 3, 10, 10) self.assumeTranslatable = wx.CheckBox(self, id = wx.ID_ANY, label = 'Assume array operations are not used') @@ -318,6 +318,15 @@ def __init__(self, parent): self.addMoccasinComments.Value = True self.addMoccasinComments.SetToolTip("Write a comment into the output file with the MOCCASIN version, time stamp, and other information.") optionGrid.Add(self.addMoccasinComments, flag = wx.ALL, border = 6) + + self.useLibSBML = wx.CheckBox(self, id = wx.ID_ANY, + label = 'Use LibSBML Conveter') + self.useLibSBML.SetToolTip("Use LibSBML to convert from raterules to reactions (if false, it will use BIOCHAM)") + self.useLibSBML.SetFont(labelFont) + self.useLibSBML.Value = True + self.useLibSBML.SetToolTip("Use LibSBML to convert from raterules to reactions (if false, it will use BIOCHAM)") + optionGrid.Add(self.useLibSBML, flag = wx.ALL, border = 6) + optionInset.Add(optionGrid, 0, wx.EXPAND, 0) self.convertButton = wx.Button(self, wx.ID_ANY, "Convert", @@ -339,29 +348,32 @@ def __init__(self, parent): panelTextFont = wx.Font(wx.NORMAL_FONT.GetPointSize() -1, 70, 90, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString) - midPanelSizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "MATLAB File"), - wx.VERTICAL) - self.matlabWebView = wx.html2.WebView.New(self, wx.ALIGN_BOTTOM|wx.ALL|wx.EXPAND ) + #midPanelSizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, "MATLAB File"), + # wx.VERTICAL) + midPanelSizer = wx.BoxSizer(wx.VERTICAL) + self.matlabWebView = wx.html2.WebView.New(self, wx.ALL|wx.EXPAND ) self.matlabWebView.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) wxSetToolTip(self.matlabWebView, "Input file for conversion") self.matlabWebView.SetFont(panelTextFont) self.matlabWebView.SetPage(_EMPTY_PAGE, "") - midPanelSizer.Add(self.matlabWebView, 1, wx.ALIGN_BOTTOM|wx.ALL|wx.EXPAND, 5) + midPanelSizer.Add(self.matlabWebView, 1, wx.ALL|wx.EXPAND, 5) mainSizer.Add(midPanelSizer, 2, wx.ALL|wx.EXPAND, 5) mainSizer.Add(buttonVSizer, 0, wx.ALL|wx.EXPAND, 0) # Bottom sizer - bottomPanelSizer = wx.StaticBoxSizer( - wx.StaticBox(self, wx.ID_ANY, "Converted File"), wx.VERTICAL) + #bottomPanelSizer = wx.StaticBoxSizer( + # wx.StaticBox(self, wx.ID_ANY, "Converted File"), wx.VERTICAL) + bottomPanelSizer = wx.BoxSizer(wx.VERTICAL) self.convertedWebView = wx.html2.WebView.New( - self, wx.ALIGN_BOTTOM|wx.ALL|wx.EXPAND) + self, wx.ALL|wx.EXPAND) self.convertedWebView.SetForegroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) self.convertedWebView.SetFont(panelTextFont) + self.convertedWebView.SetZoomType(wx.html2.WEBVIEW_ZOOM_TYPE_LAYOUT) wxSetToolTip(self.convertedWebView, "Output file after conversion") self.convertedWebView.SetPage(_EMPTY_PAGE, "") bottomPanelSizer.Add( - self.convertedWebView, 1, wx.ALIGN_BOTTOM|wx.ALL|wx.EXPAND, 5) + self.convertedWebView, 1, wx.ALL|wx.EXPAND, 5) mainSizer.Add(bottomPanelSizer, 2, wx.ALL|wx.EXPAND, 5) # Set frame sizer @@ -384,8 +396,7 @@ def __init__(self, parent): self.Bind(wx.EVT_MENU, self.OnPageSetup, id= self.pageSetup.GetId()) self.Bind(wx.EVT_MENU, self.OnPrint, id=self.printOption.GetId()) self.Bind(wx.EVT_MENU_RANGE, self.onFileHistory, id=wx.ID_FILE1, id2=wx.ID_FILE9) - self.Bind(wx.EVT_CLOSE, self.onClose) - + self.Bind(wx.EVT_CLOSE, self.onClose) def __del__(self): pass @@ -494,20 +505,31 @@ def onConvert(self, event): self.statusBar.SetStatusText("SBML format - equations", 2) # Output reaction-based SBML else: - if not have_network(): - msg = "A network connection is needed for this feature, but the network appears to be unavailable." - dlg = wx.MessageDialog(self, msg, "Warning", wx.OK | wx.ICON_WARNING) - dlg.ShowModal() - dlg.Destroy() - else: - sbml = self.controller.build_reaction_model( - use_species = self.varsAsSpecies.GetValue(), - name_after_param = False, - add_comments = self.addMoccasinComments.Value) + use_libSBML = self.useLibSBML.GetValue() + if use_libSBML: + sbml = self.controller.build_reaction_model_libsbml( + use_species = self.varsAsSpecies.GetValue(), + name_after_param = False, + add_comments = self.addMoccasinComments.Value) self.convertedWebView.SetPage(tokenize(sbml, "xml", "borland"), "") - self.statusBar.SetStatusText("SBML format - reactions", 2) + self.statusBar.SetStatusText("SBML format - reactions (libsbml)", 2) self._output_saved = False + else: + if not have_network(): + msg = "A network connection is needed for this feature, but the network appears to be unavailable." + dlg = wx.MessageDialog(self, msg, "Warning", wx.OK | wx.ICON_WARNING) + dlg.ShowModal() + dlg.Destroy() + else: + sbml = self.controller.build_reaction_model( + use_species = self.varsAsSpecies.GetValue(), + name_after_param = False, + add_comments = self.addMoccasinComments.Value) + + self.convertedWebView.SetPage(tokenize(sbml, "xml", "borland"), "") + self.statusBar.SetStatusText("SBML format - reactions (BIOCHAM)", 2) + self._output_saved = False except IOError as err: wx.EndBusyCursor() From 321adb3b69f3df724566222fd590b72e25ca70ae Mon Sep 17 00:00:00 2001 From: "Frank T. Bergmann" Date: Wed, 21 Aug 2024 16:55:12 +0200 Subject: [PATCH 2/8] add converter option --- moccasin/converter/converter.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/moccasin/converter/converter.py b/moccasin/converter/converter.py index 70a9e05..95f7abc 100755 --- a/moccasin/converter/converter.py +++ b/moccasin/converter/converter.py @@ -898,7 +898,7 @@ def valid_id(text, search=re.compile(r'\A[a-zA-Z_][a-zA-Z0-9_]*\Z').search): # ----------------------------------------------------------------------------- def create_raterule_model(parse_results, use_species=True, output_format="sbml", - name_vars_after_param=False, add_comments=True): + name_vars_after_param=False, add_comments=True, convert_to_reactions=False): # First, gather some initial information. working_context = first_function_context(parse_results) @@ -1022,6 +1022,13 @@ def create_raterule_model(parse_results, use_species=True, output_format="sbml", # (in poss-processing) a definition to the SBML produced by Biocham. document.post_add.append(('t', 'time')) + # convert to reactions + if convert_to_reactions: + prop = ConversionProperties() + prop.addOption("inferReactions", True, + "Infer reactions from rateRules in the model"); + check(document.convert(prop), 'infering reactions failed') + # Finally, return the model, with extra info about things that may need # to be added back in post-processing if we are hand this to BIOCHAM. output = generate_output(document, add_comments) From 3a1aa800fbeeace915c00a46237dd9ec86b2e4b5 Mon Sep 17 00:00:00 2001 From: "Frank T. Bergmann" Date: Wed, 21 Aug 2024 16:55:58 +0200 Subject: [PATCH 3/8] - add missing imports --- moccasin/converter/cleaner.py | 1 + moccasin/converter/expr_tester.py | 1 + moccasin/converter/finder.py | 1 + moccasin/converter/recognizer.py | 1 + 4 files changed, 4 insertions(+) diff --git a/moccasin/converter/cleaner.py b/moccasin/converter/cleaner.py index 8b14c9c..a353cb4 100644 --- a/moccasin/converter/cleaner.py +++ b/moccasin/converter/cleaner.py @@ -21,6 +21,7 @@ # ------------------------------------------------------------------------- --> import sys +import os try: thisdir = os.path.dirname(os.path.abspath(__file__)) diff --git a/moccasin/converter/expr_tester.py b/moccasin/converter/expr_tester.py index a9464a9..8ce15eb 100644 --- a/moccasin/converter/expr_tester.py +++ b/moccasin/converter/expr_tester.py @@ -22,6 +22,7 @@ import sys from collections import defaultdict +import os try: thisdir = os.path.dirname(os.path.abspath(__file__)) diff --git a/moccasin/converter/finder.py b/moccasin/converter/finder.py index 04c0132..e86bd12 100644 --- a/moccasin/converter/finder.py +++ b/moccasin/converter/finder.py @@ -21,6 +21,7 @@ # ------------------------------------------------------------------------- --> import sys +import os try: thisdir = os.path.dirname(os.path.abspath(__file__)) diff --git a/moccasin/converter/recognizer.py b/moccasin/converter/recognizer.py index ee26c0e..d4f6b6a 100644 --- a/moccasin/converter/recognizer.py +++ b/moccasin/converter/recognizer.py @@ -22,6 +22,7 @@ import sys from collections import defaultdict +import os try: thisdir = os.path.dirname(os.path.abspath(__file__)) From 048e633a020479c23470e2cd80744f06273081a0 Mon Sep 17 00:00:00 2001 From: "Frank T. Bergmann" Date: Wed, 21 Aug 2024 16:56:18 +0200 Subject: [PATCH 4/8] - fix evaluation of factorial --- moccasin/converter/evaluate_formula.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/moccasin/converter/evaluate_formula.py b/moccasin/converter/evaluate_formula.py index d855608..a04a97c 100755 --- a/moccasin/converter/evaluate_formula.py +++ b/moccasin/converter/evaluate_formula.py @@ -119,6 +119,9 @@ def __init__(self): "/": operator.truediv, "^": operator.pow } + self.int_fn = { + "factorial": math.factorial, + } self.fn = { "abs": abs, "acos": math.acos, @@ -131,7 +134,6 @@ def __init__(self): "cos": math.cos, "cosh": math.cosh, "exp": math.exp, # not working - "factorial": math.factorial, "floor": math.floor, "fix": self.fix, "ln": math.log, @@ -174,6 +176,8 @@ def evaluate_stack(self, s): return Decimal(math.e) # 2.718281828 elif op in self.fn: return Decimal(self.fn[op](self.evaluate_stack(s))) + elif op in self.int_fn: + return Decimal(self.int_fn[op](int(self.evaluate_stack(s)))) elif op in self.bin_fn: op2 = Decimal(self.evaluate_stack(s)) op1 = Decimal(self.evaluate_stack(s)) From 67187c1e691687f6dbb694f769e8864277766bf0 Mon Sep 17 00:00:00 2001 From: "Frank T. Bergmann" Date: Wed, 21 Aug 2024 16:56:32 +0200 Subject: [PATCH 5/8] ignore backup files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 957e339..39ad79f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ README.html ABOUT.html NEWS.html *.~is +*.bak \ No newline at end of file From 4331606215aa73482ad51ab5e5e2577f129f6bed Mon Sep 17 00:00:00 2001 From: "Frank T. Bergmann" Date: Wed, 21 Aug 2024 16:56:41 +0200 Subject: [PATCH 6/8] import os --- moccasin/converter/rewriter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/moccasin/converter/rewriter.py b/moccasin/converter/rewriter.py index 4eeef8a..407f869 100644 --- a/moccasin/converter/rewriter.py +++ b/moccasin/converter/rewriter.py @@ -22,6 +22,7 @@ import sys from decimal import * +import os try: thisdir = os.path.dirname(os.path.abspath(__file__)) From 65c6e0c2520b6ad5bcbebd947aec0e45ed43596e Mon Sep 17 00:00:00 2001 From: "Frank T. Bergmann" Date: Wed, 21 Aug 2024 16:57:46 +0200 Subject: [PATCH 7/8] - fix parsing issues --- moccasin/matlab_parser/context.py | 3 +- moccasin/matlab_parser/matlab.py | 11 +++--- moccasin/matlab_parser/parser.py | 60 +++++++++++++++++++++++-------- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/moccasin/matlab_parser/context.py b/moccasin/matlab_parser/context.py index 8289379..05f4be7 100755 --- a/moccasin/matlab_parser/context.py +++ b/moccasin/matlab_parser/context.py @@ -22,6 +22,7 @@ from __future__ import print_function import collections +from collections.abc import MutableMapping from pyparsing import ParseResults @@ -34,7 +35,7 @@ # # This next class def is based on http://stackoverflow.com/a/7760938/743730 -class ContextDict(collections.MutableMapping, dict): +class ContextDict(MutableMapping, dict): """Class used to implement MatlabContext properties that are dictionaries.""" def __getitem__(self, key): diff --git a/moccasin/matlab_parser/matlab.py b/moccasin/matlab_parser/matlab.py index c7f8931..d4f2abb 100644 --- a/moccasin/matlab_parser/matlab.py +++ b/moccasin/matlab_parser/matlab.py @@ -229,7 +229,7 @@ def row_to_string(row): body = MatlabNode.as_string(thing.body) return '@(' + arg_list + ')' + body elif isinstance(thing, Comment) or isinstance(thing, FunDef) \ - or isinstance(thing, Command): + or isinstance(thing, ShellCommand): # No reason for as_string called for these things, but must catch # random mayhem before falling through to the final case. return None @@ -853,10 +853,11 @@ def visit(self, node): def default_visit(self, node): """Default visitor. Users can redefine this if desired.""" - for a in type(node)._visitable_attr: - value = getattr(node, a, None) - if value: - setattr(node, a, self.visit(value)) + if isinstance(node, MatlabNode): + for a in type(node)._visitable_attr: + value = getattr(node, a, None) + if value: + setattr(node, a, self.visit(value)) return node diff --git a/moccasin/matlab_parser/parser.py b/moccasin/matlab_parser/parser.py index f05ed3a..3bb1d23 100644 --- a/moccasin/matlab_parser/parser.py +++ b/moccasin/matlab_parser/parser.py @@ -358,6 +358,7 @@ from __future__ import print_function import codecs import copy +from math import e import pdb import six import sys @@ -367,15 +368,15 @@ from distutils.version import LooseVersion from collections import defaultdict try: - from grammar_utils import * - from context import * - from matlab import * - from functions import * -except: from .grammar_utils import * from .context import * from .matlab import * from .functions import * +except: + from grammar_utils import * + from context import * + from matlab import * + from functions import * # Check minimum version of PyParsing. @@ -472,6 +473,8 @@ def visit(self, pr): msg = 'Internal grammar inconsistency: multiple tags for same construct.' raise MatlabInternalException(msg) key = first_key(pr) + if key is None: + return pr methname = 'visit_' + '_'.join(key.split()) meth = getattr(self, methname, None) if meth is None: @@ -557,6 +560,10 @@ def visit_standalone_expression(self, pr): def visit_assignment(self, pr): content = pr['assignment'] + all_keys = list(content.keys()) + if not all_keys: + content = content[0] + lvalue = self.visit(content['lhs']) rvalue = self.visit(content['rhs']) node = Assignment(lhs=lvalue, rhs=rvalue) @@ -565,6 +572,9 @@ def visit_assignment(self, pr): def visit_array(self, pr): content = pr['array'] + all_keys = list(content.keys()) + if not all_keys: + content = content[0] # Two kinds of array situations: a bare array, and one where we # managed to determine it's an array access (and not the more # ambiguous function call or array access). If we have an 'array @@ -587,6 +597,9 @@ def visit_array(self, pr): def visit_cell_array(self, pr): content = pr['cell array'] + all_keys = list(content.keys()) + if not all_keys: + content = content[0] # Two kinds of situations: a bare cell array, and one where we # managed to determine it's an array access. If we have 'row list' # in the keys, it's the former. @@ -610,7 +623,7 @@ def visit_cell_array(self, pr): def visit_array_or_function(self, pr): content = pr['array or function'] - the_name = self.visit(content['name']) + the_name = self.visit(content['name'][0]) if 'argument list' in content.keys(): the_args = self._convert_list(content['argument list']) else: @@ -620,6 +633,9 @@ def visit_array_or_function(self, pr): def visit_function_handle(self, pr): content = pr['function handle'] + all_keys = list(content.keys()) + if not all_keys: + content = content[0] if 'name' in content: return FuncHandle(name=self.visit(content['name'])) else: @@ -627,7 +643,10 @@ def visit_function_handle(self, pr): the_args = self._convert_list(content['parameter list']) else: the_args = [] - the_body = self.visit(content['function definition']) + if 'function definition' in content.keys(): + the_body = self.visit(content['function definition']) + else: + the_body = None return AnonFun(args=the_args, body=the_body) @@ -640,6 +659,10 @@ def visit_function_definition(self, pr): # in the appropriate context. content = pr['function definition'] + all_keys = list(content.keys()) + if not all_keys: + content = content[0] + name = self.visit(content['name']) params = None @@ -647,7 +670,7 @@ def visit_function_definition(self, pr): if 'parameter list' in content: params = self._convert_list(content['parameter list']) if 'output list' in content: - output = self._convert_list(content['output list']) + output = self._convert_list(content['output list'][0]) # Chicken, meet egg. So: create an incomplete FunDef, use it to # create a context object, and then set the FunDef's context field. @@ -678,12 +701,16 @@ def visit_ambiguous_id(self, pr): def visit_struct(self, pr): content = pr['struct'] + all_keys = list(content.keys()) + if not all_keys: + content = content[0] + the_base = self.visit(content['struct base']) dynamic = 'dynamic field' in content if dynamic: - the_field = self.visit(content['dynamic field']) + the_field = self.visit(content['dynamic field'][0]) else: - the_field = self.visit(content['static field']) + the_field = Identifier(self.visit(content['static field'][0])) return StructRef(name=the_base, field=the_field, dynamic=dynamic) @@ -810,7 +837,7 @@ def visit_continue_statement(self, pr): def _convert_list(self, list): - return [self.visit(thing) for thing in list] + return [self.visit(thing) for thing in list if thing is not None] def _convert_rows(self, rowlist): @@ -2107,7 +2134,9 @@ def _find_first_function(self, nodes): def _do_parse(self, input): preprocessed = self._preprocess(input) - pr = self._matlab.parseString(preprocessed, parseAll=True) + #self._matlab.debug = True + #self._matlab.verbose_stacktrace = True + pr = self._matlab.parse_string(preprocessed, parse_all=False) return self._generate_nodes_and_contexts(pr) @@ -2157,9 +2186,9 @@ def _do_parse(self, input): def _object_name(self, obj): """Returns the name of a given object.""" try: - values = MatlabParser.__dict__.iteritems() # Python 2 - except: values = MatlabParser.__dict__.items() # Python 3 + except: + values = MatlabParser.__dict__.iteritems() # Python 2 for name, thing in values: if thing is obj: return name @@ -2180,6 +2209,7 @@ def _init_grammar_names(self): # _to_print_debug = [_cell_access, _cell_array, _bare_cell, _expr] _to_print_debug = _to_name # [_fun_body, _fun_def_deep, _fun_def_shallow, _stmt, _matlab] + _to_print_debug = [_fun_body, _fun_def_deep, _fun_def_shallow, _stmt, _matlab] def _print_debug(self, print_debug=False): if print_debug: @@ -2260,7 +2290,7 @@ def parse_file(self, path, print_results=False, print_debug=False, """ self._reset() try: - file = codecs.open(path) + file = codecs.open(path, encoding='utf-8') contents = file.read() self._print_debug(print_debug) top_context = self._do_parse(contents) From 9adccbb0f4d13564585605ced5174886d0f8ab33 Mon Sep 17 00:00:00 2001 From: "Frank T. Bergmann" Date: Wed, 21 Aug 2024 16:59:23 +0200 Subject: [PATCH 8/8] - allow tests to be run from any folder also sorts a whitespace issue when comparing newlines --- tests/converter_test/run-converter-tests.py | 6 +++++- tests/evaluate_test/run-evaluate-tests.py | 8 ++++++-- tests/syntax_test/produce_test_files.py | 2 +- tests/syntax_test/run-syntax-tests.py | 9 +++++++-- tests/syntax_test/test_syntaxModule.py | 19 +++++++++++++++---- 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/tests/converter_test/run-converter-tests.py b/tests/converter_test/run-converter-tests.py index 23b0b5a..142f574 100755 --- a/tests/converter_test/run-converter-tests.py +++ b/tests/converter_test/run-converter-tests.py @@ -6,6 +6,7 @@ from pyparsing import ParseException, ParseResults sys.path.append('../..') from moccasin import * +import os sys.setrecursionlimit(10000) @@ -16,7 +17,10 @@ def main(argv): quiet=False print_parse=True - for path in glob.glob("converter-test-cases/valid*.m"): + current_dir = os.path.dirname(os.path.abspath(__file__)) + test_cases_dir = os.path.join(current_dir, "converter-test-cases") + + for path in glob.glob(os.path.join(test_cases_dir, "valid*.m")): file = open(path, 'r') file_contents = file.read() file.close() diff --git a/tests/evaluate_test/run-evaluate-tests.py b/tests/evaluate_test/run-evaluate-tests.py index db659d3..dc7c31d 100644 --- a/tests/evaluate_test/run-evaluate-tests.py +++ b/tests/evaluate_test/run-evaluate-tests.py @@ -29,7 +29,9 @@ sys.path.append('../../moccasin/converter/') sys.path.append('../../moccasin/') -from evaluate_formula import * +#from evaluate_formula import * +from moccasin.converter.evaluate_formula import * +import os def main(argv): @@ -48,7 +50,9 @@ def main(argv): do_print = any(['-v' in y for y in options]) do_debug = not any(['-n' in y for y in options]) - for f in glob.glob("evaluate-test-cases/valid_*.m"): + script_location = os.path.dirname(os.path.abspath(__file__)) + + for f in glob.glob(os.path.join(script_location, "evaluate-test-cases/valid_*.m")): print('===== ' + f + ' ' + '='*30) with open(f, 'r') as file_in: contents = file_in.read() diff --git a/tests/syntax_test/produce_test_files.py b/tests/syntax_test/produce_test_files.py index c57aebb..a3859ad 100755 --- a/tests/syntax_test/produce_test_files.py +++ b/tests/syntax_test/produce_test_files.py @@ -27,7 +27,7 @@ import getopt from pyparsing import ParseException, ParseResults sys.path.append('../../moccasin/') -from matlab_parser import * +from moccasin import * def main(argv): '''Usage: run-syntax-tests.py [-d] [-v] diff --git a/tests/syntax_test/run-syntax-tests.py b/tests/syntax_test/run-syntax-tests.py index 1f8fa6e..2bdc32c 100755 --- a/tests/syntax_test/run-syntax-tests.py +++ b/tests/syntax_test/run-syntax-tests.py @@ -27,7 +27,9 @@ import getopt from pyparsing import ParseException, ParseResults sys.path.append('../../moccasin/') -from matlab_parser import * +sys.path.append('../..') +from moccasin import * +import os def main(argv): @@ -45,7 +47,10 @@ def main(argv): do_debug = not any(['-n' in y for y in options]) do_print = any(['-v' in y for y in options]) - for f in glob.glob("syntax-test-cases/valid*.m"): + script_location = os.path.dirname(os.path.abspath(__file__)) + test_cases_path = os.path.join(script_location, "syntax-test-cases") + + for f in glob.glob(os.path.join(test_cases_path, "valid*.m")): print('===== ' + f + ' ' + '='*30) contents = '' with open(f, 'r') as file: diff --git a/tests/syntax_test/test_syntaxModule.py b/tests/syntax_test/test_syntaxModule.py index 287583a..837fed4 100755 --- a/tests/syntax_test/test_syntaxModule.py +++ b/tests/syntax_test/test_syntaxModule.py @@ -13,7 +13,8 @@ sys.path.append('moccasin/') sys.path.append('../moccasin') sys.path.append('../../moccasin') -from matlab_parser import MatlabParser +from moccasin import * +import traceback _VERSION2 = platform.python_version().startswith('2') @@ -30,21 +31,26 @@ def pytest_generate_tests(metafunc): def build_model(path): try: with MatlabParser() as parser: - results = parser.parse_file(path, fail_soft=True) + results = parser.parse_file(path, fail_soft=True, print_debug=False) parser.print_parse_results(results, print_raw=True) except Exception as e: print(e) + traceback.print_exc() + raise e #reads file containing expected parsed model and returns it as string def read_parsed(path): file = codecs.open(path, encoding='utf-8') contents = file.read() file.close() - return contents + return contents.replace('\r\n', '\n') # Constructs the params dictionary for test function parametrization def obtain_params(): - if os.path.isdir('tests'): + this_dir = os.path.dirname(os.path.abspath(__file__)) + if os.path.isdir(this_dir + '/syntax-test-cases'): + path = [this_dir + '/syntax-test-cases'] + elif os.path.isdir('tests'): path = ['tests', 'syntax_test', 'syntax-test-cases'] elif os.path.isdir('syntax_test'): path = ['syntax_test', 'syntax-test-cases'] @@ -82,4 +88,9 @@ def test_syntaxCases(self, capsys, model, parsed): print("---Ouput from parser---") print(repr(from_parser)) print ("\n \n") + if from_parser != from_file: + print("Parser output does not match expected output") assert from_parser == from_file + +if __name__ == "__main__": + pytest.main(sys.argv) \ No newline at end of file