1    	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2    	/*                                                                           */
3    	/*                  This file is part of the class library                   */
4    	/*       SoPlex --- the Sequential object-oriented simPlex.                  */
5    	/*                                                                           */
6    	/*  Copyright (c) 1996-2023 Zuse Institute Berlin (ZIB)                      */
7    	/*                                                                           */
8    	/*  Licensed under the Apache License, Version 2.0 (the "License");          */
9    	/*  you may not use this file except in compliance with the License.         */
10   	/*  You may obtain a copy of the License at                                  */
11   	/*                                                                           */
12   	/*      http://www.apache.org/licenses/LICENSE-2.0                           */
13   	/*                                                                           */
14   	/*  Unless required by applicable law or agreed to in writing, software      */
15   	/*  distributed under the License is distributed on an "AS IS" BASIS,        */
16   	/*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */
17   	/*  See the License for the specific language governing permissions and      */
18   	/*  limitations under the License.                                           */
19   	/*                                                                           */
20   	/*  You should have received a copy of the Apache-2.0 license                */
21   	/*  along with SoPlex; see the file LICENSE. If not email to soplex@zib.de.  */
22   	/*                                                                           */
23   	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
24   	
25   	/**@file  mpsinput.cpp
26   	 * @brief Read MPS format files.
27   	 */
28   	
29   	#include <assert.h>
30   	#include <ctype.h>
31   	#include <string.h>
32   	
33   	#include "soplex/spxdefines.h"
34   	#include "soplex/mpsinput.h"
35   	#include "soplex/spxout.h"
36   	
37   	#define PATCH_CHAR    '_'
38   	#define BLANK         ' '
39   	
40   	namespace soplex
41   	{
42   	
43   	/// fill the line from \p pos up to column 80 with blanks.
44   	static void clear_from(char* buf, int pos)
45   	{
46   	   for(int i = pos; i < 80; i++)
47   	      buf[i] = BLANK;
48   	
49   	   buf[80] = '\0';
50   	}
51   	
52   	/// change all blanks inside a field to #PATCH_CHAR.
53   	static void patch_field(char* buf, int beg, int end)
54   	{
55   	   while((beg <= end) && (buf[end] == BLANK))
56   	      end--;
57   	
58   	   while((beg <= end) && (buf[beg] == BLANK))
59   	      beg++;
60   	
61   	   for(int i = beg; i <= end; i++)
62   	      if(buf[i] == BLANK)
63   	         buf[i] = PATCH_CHAR;
64   	}
65   	
66   	/// read a MPS format data line and parse the fields.
67   	bool MPSInput::readLine()
68   	{
69   	   int   len;
70   	   int   space;
71   	   char* s;
72   	   bool  is_marker;
73   	   bool  is_comment;
74   	
75   	   do
76   	   {
77   	      m_f0 = m_f1 = m_f2 = m_f3 = m_f4 = m_f5 = 0;
78   	      is_marker = false;
79   	
80   	      // Read until we have a non-empty, non-comment line.
81   	      do
82   	      {
83   	         if(!m_input.getline(m_buf, sizeof(m_buf)).good() && !m_input.eof())
84   	            return false;
85   	
86   	         m_lineno++;
87   	
88   	         SPxOut::debug(this, "DMPSIN01 Line {} {}\n", m_lineno, m_buf);
89   	
90   	         /* check if comment line */
91   	         is_comment = true;
92   	
93   	         if(m_buf[0] == '*')
94   	            continue;
95   	
96   	         /* normalize line and check if it is empty */
97   	         len = int(strlen(m_buf));
98   	
99   	         for(int i = 0; i < len; i++)
100  	         {
101  	            if(m_buf[i] == '\t' || m_buf[i] == '\n' || m_buf[i] == '\r')
102  	               m_buf[i] = BLANK;
103  	            else if(m_buf[i] != BLANK)
104  	               is_comment = false;
105  	         }
106  	      }
107  	      while(is_comment);
108  	
109  	      len = int(strlen(m_buf));
110  	
111  	      if(len < 80)
112  	         clear_from(m_buf, len);
113  	
114  	      assert(strlen(m_buf) >= 80);
115  	
116  	      /* Look for new section
117  	       */
118  	      if(*m_buf != BLANK)
119  	      {
120  	         m_f0 = strtok(&m_buf[0], " ");
121  	
122  	         assert(m_f0 != 0);
123  	
124  	         m_f1 = strtok(0, " ");
125  	
126  	         return true;
127  	      }
128  	
129  	      if(!m_is_new_format)
130  	      {
131  	         /* Test for fixed format comments
132  	          */
133  	         if((m_buf[14] == '$') && (m_buf[13] == ' '))
134  	            clear_from(m_buf, 14);
135  	         else if((m_buf[39] == '$') && (m_buf[38] == ' '))
136  	            clear_from(m_buf, 39);
137  	
138  	         /* Test for fixed format
139  	          */
140  	         space = m_buf[12] | m_buf[13]
141  	                 | m_buf[22] | m_buf[23]
142  	                 | m_buf[36] | m_buf[37] | m_buf[38]
143  	                 | m_buf[47] | m_buf[48]
144  	                 | m_buf[61] | m_buf[62] | m_buf[63];
145  	
146  	         if(space == BLANK || len < 13)
147  	         {
148  	            /* Now we have space at the right positions.
149  	             * But are there also the non space where they
150  	             * should be ?
151  	             */
152  	            bool number = isdigit(m_buf[24]) || isdigit(m_buf[25])
153  	                          || isdigit(m_buf[26]) || isdigit(m_buf[27])
154  	                          || isdigit(m_buf[28]) || isdigit(m_buf[29])
155  	                          || isdigit(m_buf[30]) || isdigit(m_buf[31])
156  	                          || isdigit(m_buf[32]) || isdigit(m_buf[33])
157  	                          || isdigit(m_buf[34]) || isdigit(m_buf[35]);
158  	
159  	            /* len < 13 is handle ROW lines with embedded spaces
160  	             * in the names correctly
161  	             */
162  	            if(number || len < 13)
163  	            {
164  	               /* Now we assume fixed format, so we patch possible embedded spaces.
165  	                */
166  	               patch_field(m_buf,  4, 12);
167  	               patch_field(m_buf, 14, 22);
168  	               patch_field(m_buf, 39, 47);
169  	            }
170  	            else
171  	            {
172  	               if(m_section == COLUMNS || m_section == RHS
173  	                     || m_section == RANGES  || m_section == BOUNDS)
174  	                  m_is_new_format = true;
175  	            }
176  	         }
177  	         else
178  	         {
179  	            m_is_new_format = true;
180  	         }
181  	      }
182  	
183  	      s = &m_buf[1];
184  	
185  	      /* At this point it is not clear if we have a indicator field.
186  	       * If there is none (e.g. empty) f1 will be the first name field.
187  	       * If there is one, f2 will be the first name field.
188  	       *
189  	       * Initially comment marks '$' ar only allowed in the beginning
190  	       * of the 2nd and 3rd name field. We test all fields but the first.
191  	       * This makes no difference, since if the $ is at the start of a value
192  	       * field, the line will be errornous anyway.
193  	       */
194  	      do
195  	      {
196  	         if(0 == (m_f1 = strtok(s, " ")))
197  	            break;
198  	
199  	         if((0 == (m_f2 = strtok(0, " "))) || (*m_f2 == '$'))
200  	         {
201  	            m_f2 = 0;
202  	            break;
203  	         }
204  	
205  	         if(!strcmp(m_f2, "'MARKER'"))
206  	            is_marker = true;
207  	
208  	         if((0 == (m_f3 = strtok(0, " "))) || (*m_f3 == '$'))
209  	         {
210  	            m_f3 = 0;
211  	            break;
212  	         }
213  	
214  	         if(is_marker)
215  	         {
216  	            if(!strcmp(m_f3, "'INTORG'"))
217  	               m_is_integer = true;
218  	            else if(!strcmp(m_f3, "'INTEND'"))
219  	               m_is_integer = false;
220  	            else
221  	               break; // unknown marker
222  	         }
223  	
224  	         if(!strcmp(m_f3, "'MARKER'"))
225  	            is_marker = true;
226  	
227  	         if((0 == (m_f4 = strtok(0, " "))) || (*m_f4 == '$'))
228  	         {
229  	            m_f4 = 0;
230  	            break;
231  	         }
232  	
233  	         if(is_marker)
234  	         {
235  	            if(!strcmp(m_f4, "'INTORG'"))
236  	               m_is_integer = true;
237  	            else if(!strcmp(m_f4, "'INTEND'"))
238  	               m_is_integer = false;
239  	            else
240  	               break; // unknown marker
241  	         }
242  	
243  	         if((0 == (m_f5 = strtok(0, " "))) || (*m_f5 == '$'))
244  	            m_f5 = 0;
245  	      }
246  	      while(false);
247  	   }
248  	   while(is_marker);
249  	
250  	   SPxOut::debug(this, "DMPSIN02 -----------------------------------------------\n"
251  	                 "DMPSIN03 f0={}\n"
252  	                 "DMPSIN04 f1={}\n"
253  	                 "DMPSIN05 f2={}\n"
254  	                 "DMPSIN07 f4={}\n"
255  	                 "DMPSIN06 f3={}\n"
256  	                 "DMPSIN08 f5={}\n"
257  	                 "DMPSIN09 -----------------------------------------------\n",
258  	                 ((m_f0 == 0) ? "nil" : m_f0),
259  	                 ((m_f1 == 0) ? "nil" : m_f1),
260  	                 ((m_f2 == 0) ? "nil" : m_f2),
261  	                 ((m_f3 == 0) ? "nil" : m_f3),
262  	                 ((m_f4 == 0) ? "nil" : m_f4),
263  	                 ((m_f5 == 0) ? "nil" : m_f5));
264  	   return true;
265  	}
266  	
267  	/// Insert \p name as field 1 and shift all other fields up.
268  	void MPSInput::insertName(const char* name, bool second)
269  	{
270  	   m_f5 = m_f4;
271  	   m_f4 = m_f3;
272  	   m_f3 = m_f2;
273  	
274  	   if(second)
275  	      m_f2 = name;
276  	   else
277  	   {
278  	      m_f2 = m_f1;
279  	      m_f1 = name;
280  	   }
281  	}
282  	
283  	} // namespace soplex
284