home *** CD-ROM | disk | FTP | other *** search
/ C/C++ Interactive Guide / c-cplusplus-interactive-guide.iso / c_ref / csource4 / 299_01 / mel.art < prev    next >
Encoding:
Text File  |  1989-12-30  |  21.2 KB  |  387 lines

  1. MEL - a Metalanguage Data Processor
  2.  
  3.  
  4.  
  5. INTRODUCTION:
  6.  
  7. As a mechanical engineer, my experiences with analysis programs are in the areas of structural stress, fluid dynamics, heat conduction, and thermal/hydraulic system simulation.  Such programs present the technical software developer with a number of unique problems, not least of which is providing a user-friendly interface.  That's because even though program users tend to be computer literate, input data can be voluminous and tedious (and error prone) to prepare.  Also, the user typically makes many runs with only slight changes; as design optimization is often accomplished by repeated analysis.  Another consideration is that both input and output must be stored and presented in a manner that allows independent verification and validation by a checker or some official entity.  Finally, the information output from one program may be required as input by another.
  8.  
  9. Another big headache is that modern (i.e., graphical) user interfaces tend to be hardware or system-software specific.  What is needed is a universal interface that frees the developer from the nuances of different types of machines and operating systems, while at the same time representing a standard that machine-specific routines can work with.
  10.  
  11. A solution I have arrived at for making such technical programs more user-friendly and modularized is called MEL.  MEL (which stands for MEtaLanguage data processor) is a set of input/output (I/O) utilities that provides a standard interface between the program and the user.  It can translate input data written in "pseudo-English" (Example 1) and makes it available to the program as variables (Example 2).  It can also take program variables (Example 3) and translate them into pseudo-English (Example 4).  Effort was taken in providing data objects that could easily be incorporated into almost any engineering analysis program (Example 5).  
  12.  
  13. You should find the MEL interface useful in developing your own technical programs.  Firstly, the pseudo-English look of MEL means that I/O will be more readable and comprehensible to the user (or checker).  Secondly, MEL is object oriented in that it provides a structured and encapsulated I/O interface (more about this later).  Thus, your development time will be reduced and future changes can be made to your program more easily.  Thirdly, MEL's grammar is simple and unambiguous, with both input and output formats being identical.  This means output from one program may serve directly as input to another.  Finally, MEL can read and write data directly to a file so that a permanent record of a run and its results are available. 
  14.  
  15. DESCRIPTION OF MEL:
  16.  
  17. The smallest unit of pseudo-English I/O in MEL is called a "descriptor."  Its purpose is to describe something, either data or a command, to a program.  The general format for descriptors is much like function calls in typical programming languages.  An I/O unit consists of a descriptor name (somewhat like a function name), followed by a parameter list, followed by an end-of-unit symbol (the semicolon).  For example, consider the following MEL descriptor which could be used as part of the input to a piping network analysis program I have written: 
  18.  
  19.     pipe, length = 100 (ft), diameter = 6 (in);
  20.  
  21. This is a "pipe" descriptor whose parameters are "length" and "diameter".  The values assigned to these parameters would be 100 and 6, and in units of feet and inches, respectively.  Although the tokens (names and parameters) making up descriptors are customized by the developer for each individual application program, the above grammar remains the same for all programs using MEL.  (For more, see Examples 1 and 4.) 
  22.  
  23. The format for MEL was chosen for its simplicity, while allowing for as much flexibility as possible without introducing ambiguity.  I won't list all of the details here, but let me just say: tokens may be abbreviated (shortened, as long as they remain uniquely identifiable), a default parameter order will be assumed if parameter names are missing, comments may be included by enclosing them in double quotes, parameter values may be labeled as "unknown," etc.  The point was to make programs incorporating MEL as convenient to the user as possible. 
  24.  
  25. INCORPORATING MEL: 
  26.  
  27. In order to incorporate MEL into one of your own programs you must first create a "dictionary" for both input and output that defines the proper spelling, number, and types (integer, array, etc.) of data associated with each descriptor and parameter.  This is done by customizing the mel.h header file which you then include in your application source code file.  (Note that by simply changing spellings in the dictionary you could go from pseudo-English to "pseudo-French" or some other "pseudo-language.")  The task of defining dictionaries has been made as painless as possible by giving complete instructions and an example program on the MEL diskette.  (MEL is available through the CUG library.  The diskette contains MEL source code, header file, documentation and instructions, an example program, and a conversion factor routine.  Since a listing of all MEL routines would run over 50 pages, a complete listing has not been included with this article.)  You will also need to prepare documentation defining the dictionaries to the user and explaining what the tokens mean.  This can be included with your other user documentation. 
  28.  
  29. To obtain data from a descriptor you must first read it, and then extract the data.  One way of doing this is shown in Example 2.  An example of outputing data is shown in Example 3.  If you wish to allow the user to input data with different units, conversion to internal units will be required (ASTM, 1982).  Included on the MEL diskette is a routine that can convert between more than 150 different units.  Additional units and conversion factors can easily be added to the source code. 
  30.  
  31. HOW MEL WAS DEVELOPED:
  32.  
  33. An early decision was to write MEL in C.  FORTRAN is the traditional language for scientific programs; however, engineers like myself are beginning to realize that there is more to technical software development than simply correctly coding a complex algorithm.  ANSI C has a number of significant nonnumerical advantages over FORTRAN (Kempf, 1987).  C is a popular and standardized language that, in my opinion, allows for more flexible structured programing and data encapsulation techniques to be applied (also see Jeffery, 1989).  C has more operators and program control constructs than FORTRAN.  C allows indirection (pointers) where FORTRAN does not.  C more easily interfaces to existing system software since much of this software is itself written in C.  Also, C is a popular language for unconventional computer architectures such as parallel processors (Lusk, 1987) and neural networks.  (Let me also mention some of C's shortcomings, which are related to its relative naivete for scientific purposes.  Dynamic array dimensioning in C is convoluted (Press, 1988).  C does not have the numerical library that FORTRAN does.  And finally, C does not allow operator overloading for data structures (complex numbers for example) nor does it have an exponentiation operator.  However, I do not think these defects in C are not difficult to overcome.) 
  34.  
  35. Another decision was to "objectify" MEL.  (Object oriented programing is certainly the latest fad, and the arguments its adherents present for its advantages have not yet been disproven.  They may even be true.  Partly as an experiment to form my own opinion, the design of MEL incorporates the object oriented paradigm.)  Since C was not designed as an object oriented language, but, nevertheless, is a very flexible one, there were many ways in which I could have expressed the concept.  I chose to make use of C's preprocessor to restrict the visibility of public type, function, and data declarations to just those objects your application program may need at a certain place.  To see, in a general manner, how this was done consider Example 5.  (The private type, function, and variable data needed by the MEL routines themselves are not shown in the example and are hidden from your program by use of other defined/undefined manifest constants.)  For another approach refer to the article by Jeffery.
  36.  
  37. SUMMARY AND FUTURE ENHANCEMENTS:
  38.  
  39. Software engineering is rapidly evolving and it seems everyone has his or her own ideas about what makes a good user-interface.  I believe MEL is a practical answer to the spectrum of interface problems confronting either the developer or the user of complex technical programs. 
  40.  
  41. Some may criticize MEL on its verbosity (as compared to FORTRAN's fixed field format), the necessity of the user spending time learning how to use MEL (versus a more interactive interface), and the somewhat clumsy way objects must be (or at least, were) encoded in C.  These complaints are legitimate and they are inherent in MEL's design.  All that can be said is that no design can be all things to all people.  A design's strength can also be viewed as its weakness.  
  42.  
  43. Since few programmers are familiar with C++, it may be some time before it is worthwhile to port MEL to a "real" object oriented language.  Thus, the next steps in the evolution of MEL would seem to be incorporating it into a language sensitive editor, a graphical output post-processor, and perhaps later, into an expert system shell specialized for the type of analysis being performed. 
  44.  
  45. BIBLIOGRAPHY: 
  46.  
  47. George M. Crews, "HAPN - A Hydraulic Analysis of Piping Networks Program," Masters Thesis in Engineering Science, University of Tennessee, Knoxville, 1989.  A portion of this thesis describes MEL and how it was developed and used for a specific analysis program. 
  48.  
  49. David Jeffery, "Object-Oriented Programming in ANSI C," Computer Language Magazine, February, 1989.  This article discusses the object oriented paradigm and a way to implement it in C. 
  50.  
  51. James Kempf, Numerical Software Tools in C, Prentice-Hall, Inc., 1987.  This book contains an introduction to both numerical programming and C.  The emphasis of the text is on creating small routines that can be used as building blocks for larger programs.  Possible shortcomings are its lack of data hiding and that it treats doubly dimensioned arrays statically rather than dynamically. 
  52.  
  53. Ewing Lusk, Overbeek, et al., Portable Programs for Parallel Processors,  Holt, Reinhart and Winston, Inc., 1987.  This book describes a set of C tools for use on a broad range of parallel machines. 
  54.  
  55. William H. Press, Flannery, et al., Numerical Recipes in C, Cambridge University Press, 1988.  Based on an earlier FORTRAN edition, this is a great cookbook giving a wide range of oven-tested recipes for the numerical gourmet.  It shows the correct way to handle multidimensioned arrays (dynamically).  A complaint sometimes heard is that a few of the algorithms are getting obsolete due to rapid advances in numerical techniques being made. 
  56.  
  57. ASTM E 380-82 Standard for Metric Practice, American Society for Testing Materials, 1982.  This standard contains many useful conversion factors between English and metric units. 
  58.  
  59. Example 1.  An Example of MEL Input for a Hydraulic Analysis Program.  (Note that tokens will be unique to each application.) 
  60.  
  61. title, 'Example Problem Illustrating MEL'; 
  62. fluid, "water" 
  63.     density = 62.4 (lbm/ft3), 
  64.     viscosity = 1 (cp); 
  65. node, 1, pressure = 8.67 (psi); "20 ft of water" 
  66. branch, 100, from_node = 1, to_node = 2; 
  67.     pipe, 
  68.         length = 100 (ft), 
  69.         id = 6 (in), 
  70.         material = steel; 
  71. end_of_branch; 
  72. node, 2, pressure = 6.5 (psi); "15 ft of water" 
  73. next; 
  74.  
  75.  
  76. Example 2.  Example of Obtaining Data From a MEL Descriptor:
  77.  
  78.  
  79. Descriptor:
  80.     
  81. pipe, length = 100 (ft), diameter = 6 (in);
  82.     
  83. Code fragment:
  84.     
  85. double pipe_length, diameter;
  86.     
  87. union meli_param_data data;   /* see Example 5. */
  88. char units[MAX_STRING_LEN+1];
  89. int array_len;
  90. int unknown_flag;
  91.     
  92. meli();  /* reads descriptor */
  93.     
  94. meli_data("length", &data, units, &array_len,
  95.     &unknown_flag);  /* gets pipe length */
  96. pipe_length = data.real;  /* will equal 100 */
  97.  
  98. meli_data("diameter", &data, units, &array_len,
  99.     &unknown_flag);  /* gets pipe diameter */
  100. diameter = data.real;  /* will equal 6 */
  101.  
  102. /* note that units, array_len, and unknown_flag
  103.    are not considered (used). */
  104.  
  105.  
  106. Example 3.  Example of Outputting a MEL descriptor:
  107.  
  108. Code Fragment:
  109.  
  110. double pipe_length = 100, diameter = 6;
  111.     
  112. union melo_param_data data;   /* see Example 5. */
  113. char length_units[] = "ft";
  114. char diameter_units[] = "in";
  115. int array_len = 0;
  116. int unknown_flag = 0;
  117.  
  118. melo_init("pipe");  /* initialize */
  119.  
  120. /* get data ready to output: */
  121. data.real = pipe_length;
  122. melo_data("length", &data, length_units, array_len, 
  123.     unknown_flag);
  124. data.real = diameter;
  125. melo_data("diameter", &data, diameter_units, 
  126.     array_len, unknown_flag);
  127.  
  128. melo();  /* translates data into string */
  129.  
  130. Descriptor:
  131.  
  132. pipe, 
  133.     length = 100 (ft), 
  134.     diameter = 6 (in);
  135.  
  136.  
  137. Example 4.  An Example of Output Generated by a Hydraulic Analysis Program using MEL.  (From the input data given in Example 1.)
  138.  
  139.  
  140. program, 
  141.     name = 'HAPN - Hydraulic Analysis of Piping Networks', 
  142.     problem_title = 'Example Problem Illustrating MEL'; 
  143. message, 
  144.     text = 'Date: Thu Jul 13 09:02:11 1989'; 
  145. message, 
  146.     text = 'Input filename: input'; 
  147. equations, 
  148.     node = 0, 
  149.     loop = 0, 
  150.     iterations = 7; 
  151. branch, 
  152.     number = 100, 
  153.     type = 'independent_branch', 
  154.     flow_rate = 436238 (lbm/h), 
  155.     flow_change = -6.20476e-007 (%), 
  156.     flow_dp = 2.17 (psi), 
  157.     elevation_dp = 0 (psi); 
  158. component, 
  159.     branch_number = 100, 
  160.     component_number = 0, 
  161.     type = 'pipe', 
  162.     resistance = 4.95228 (Pa*s2/kg2), 
  163.     change_resistance = -1.24095e-008 (%), 
  164.     pressure_drop = 2.17 (psi); 
  165. node, 
  166.     number = 1, 
  167.     pressure = 8.67 (psi); 
  168. node, 
  169.     number = 2, 
  170.     pressure = 6.5 (psi); 
  171. next; 
  172.  
  173.  
  174. Example 5.  Public Interface Between MEL and Any Application Program Using It.  (Excerpted from mel.h header file.) 
  175.  
  176. /* if using MEL for input (#define MEL_INPUT), then must 
  177.    define the MEL input data object: */ 
  178. #ifdef MEL_INPUT 
  179.         
  180. /* firstly, define input constants (all must be 
  181.    CUSTOMIZED for specific application program): */ 
  182.         
  183. #define MELI_MAX_DESCRIP_STR_LEN 256 
  184.     /* maximum number of characters in any input descriptor 
  185.        string. */ 
  186. #define MELI_MAX_PARAMS 6 
  187.     /* maximum number of parameters for any descriptor (min 
  188.        num = 1). */ 
  189. #define MELI_MAX_PARAM_STR_LEN 80 
  190. #define MELI_MAX_PARAM_ARRAY_STR_LEN 1 
  191.     /* largest allowable parameter string lengths (min size 
  192.        = 1) */ 
  193. #define MELI_MAX_PARAM_INT_ARRAY_LEN 1 
  194. #define MELI_MAX_PARAM_REAL_ARRAY_LEN 1 
  195. #define MELI_MAX_PARAM_STR_ARRAY_LEN 1 
  196.     /* maximum number of elements in parameter data arrays 
  197.        (min = 1). */ 
  198. #define MELI_UNITS_STR_LEN 80 
  199.     /* maximum length of units associated with any param 
  200.        (min = 1) */ 
  201.         
  202. /* secondly, define input data structures: */ 
  203.         
  204. union meli_param_data { 
  205.     int integer;  /* also holds boolean type */
  206.     double real; 
  207.     char string[MELI_MAX_PARAM_STR_LEN+1]; 
  208.     int integer_array[MELI_MAX_PARAM_INT_ARRAY_LEN]; 
  209.     double real_array[MELI_MAX_PARAM_REAL_ARRAY_LEN]; 
  210.     char string_array[MELI_MAX_PARAM_STR_ARRAY_LEN] 
  211.                 [MELI_MAX_PARAM_ARRAY_STR_LEN+1]; 
  212. }; 
  213. /* this is used for input parameter data. it may either be 
  214.    an integer, real, string, array of integers, array of 
  215.    reals, or an array of strings. (to save space a union was 
  216.    used.) */ 
  217.         
  218. /* thirdly, define input variables: */ 
  219.         
  220. char meli_descriptor_string[MELI_MAX_DESCRIP_STR_LEN+1]; 
  221.     /* global storage for the input descriptor string. */ 
  222.         
  223. /* lastly, define input functions (typically they return 0 
  224.    if no error encountered, else some nonzero error 
  225.    code): */ 
  226.         
  227. int meli_file(FILE *meli_file_handle); 
  228.     /* read a descriptor string from the input stream and 
  229.        call meli(). also, put copy of string read into 
  230.        meli_descriptor_string. */ 
  231. int meli(void); 
  232.     /* translate meli_descriptor_string and put information 
  233.        into a private data structure (meli_datum). */ 
  234. char *meli_descrip_type(void); 
  235.     /* return pointer to name of type of descriptor read by 
  236.        meli(). */ 
  237. int meli_num_params(void); 
  238.     /* return number of parameters read by meli(). */ 
  239. int meli_param(int param_num, char *param, union 
  240.     meli_param_data *data, char *units, int *array_len, int 
  241.     *unknown_flag); 
  242.     /* fill arguement list with param_num'th parameter read 
  243.        by meli(). (start with param_num = 0.) */ 
  244. int meli_data(char *param, union meli_param_data *data, 
  245.     char *units, int *array_len, int *unknown_flag); 
  246.     /* see if *param was input. if it was, then fill 
  247.        argument list with data from meli_datum. */ 
  248.  
  249. #endif /* MEL_INPUT */ 
  250.         
  251. /* if using MEL for output, must define the MEL output data 
  252.    object: */ 
  253. #ifdef MEL_OUTPUT 
  254.         
  255. /* firstly, define output constants (all must be 
  256.    CUSTOMIZED): */ 
  257.         
  258. #define MELO_MAX_DESCRIP_STR_LEN 256 
  259.     /* how many characters can be in an output descriptor 
  260.        string? */ 
  261. #define MELO_MAX_PARAMS 6 
  262.     /* maximum number of parameters for any descriptor. */ 
  263. #define MELO_MAX_PARAM_STR_LEN 80 
  264. #define MELO_MAX_PARAM_ARRAY_STR_LEN 1 
  265.     /* largest allowable parameter string length. */ 
  266. #define MELO_MAX_PARAM_INT_ARRAY_LEN 1 
  267. #define MELO_MAX_PARAM_REAL_ARRAY_LEN 1 
  268. #define MELO_MAX_PARAM_STR_ARRAY_LEN 1 
  269.     /* maximum number of elements in array of parameter 
  270.        data. */ 
  271. #define MELO_UNITS_STR_LEN 80 
  272.     /* maximum string length of any units associated with a 
  273.        param. */ 
  274.         
  275. /* secondly, define output data structures: */ 
  276.         
  277. union melo_param_data { 
  278.     int integer; 
  279.     double real; 
  280.     char string[MELO_MAX_PARAM_STR_LEN+1]; 
  281.     int integer_array[MELO_MAX_PARAM_INT_ARRAY_LEN]; 
  282.     double real_array[MELO_MAX_PARAM_REAL_ARRAY_LEN]; 
  283.     char string_array[MELO_MAX_PARAM_STR_ARRAY_LEN] 
  284.                 [MELO_MAX_PARAM_ARRAY_STR_LEN+1]; 
  285. }; 
  286. /* this is for output parameter data. it may either be an 
  287.    integer, real, string, array of integers, array of reals, or 
  288.    an array of strings. (to save space a union was used.) */ 
  289.         
  290. /* thirdly, define output variables: */ 
  291.         
  292. char melo_descriptor_string[MELO_MAX_DESCRIP_STR_LEN+1]; 
  293.     /* global storage for the output descriptor string. */ 
  294.         
  295. /* lastly, define output functions (typically return 0 if no 
  296.    error): */ 
  297.         
  298. int melo_init(char *descrip_type); 
  299.     /* initialize private data structure (melo_datum) to 
  300.        accept parameter data from following functions. 
  301.        output descriptor type will be descrip_type. returns 
  302.        0 if no errors were encountered. */ 
  303. int melo_data(char *param, union melo_param_data *data, char 
  304.     *units, int array_len, int unknown_flag); 
  305.     /* put data for parameter *param into the proper place 
  306.        in melo_datum. returns zero if no errors were 
  307.        encountered. */ 
  308. void melo(int melo_verbose_flag); 
  309.     /* takes the information in melo_datum and translates it 
  310.        into melo_descriptor_string. user must set 
  311.        melo_verbose_flag = 1 to make output as readable as 
  312.        possible, set it equal to zero to make output as 
  313.        terse as possible (and still remain in MEL 
  314.        format). */ 
  315. int melo_file(FILE *melo_file_handle, int melo_verbose_flag); 
  316.     /* take the information in melo_datum, translate it into 
  317.        melo_descriptor_string, and output it to file. */ 
  318.  
  319. #endif /* MEL_OUTPUT */
  320.  
  321. /* now define data objects common to both input and 
  322.    output: */ 
  323.  
  324. /* if an error occurs, MEL will try and tell you what 
  325.    happened. so define required error handling 
  326.    information: */ 
  327.  
  328. #define MEL_MAX_ERR_MSG_LEN 80 
  329.  
  330. struct mel_errors { 
  331.     enum {   /* which error occured? */ 
  332.         mel_no_err,
  333.         mel_read_err, 
  334.         mel_write_err, 
  335.         mel_end_of_file_err, 
  336.         mel_end_of_data_err, 
  337.         mel_syntax_err, 
  338.         mel_unknown_descrip_name_err, 
  339.         mel_unknown_param_name_err, 
  340.         mel_missing_param_name_err,         
  341.         mel_param_data_err, 
  342.         mel_missing_paren_err, 
  343.         mel_too_many_param_err,         
  344.         mel_missing_bracket_err, 
  345.     } type; 
  346.     int start_line;   /* on which lines did err occur? */ 
  347.     int end_line;     /* (meaningful for input only.) */ 
  348.     char msg[MEL_MAX_ERR_MSG_LEN+1];   
  349.             /* additional info describing err */ 
  350. } mel_err;  /* (not same as messages below). */ 
  351.  
  352. #define MEL_MAX_NUM_ERR_MESSAGES 13 
  353.  
  354. #ifdef MEL_INIT 
  355.  
  356. /* the following describes each type of enumerated error: */ 
  357. char mel_err_msg[MEL_MAX_NUM_ERR_MESSAGES]
  358.             [MEL_MAX_ERR_MSG_LEN+1] 
  359.     ={"No errors encountered", 
  360.       "Can't read file", 
  361.       "Can't write file", 
  362.       "Unexpected end of file encountered", 
  363.       "End of input data encountered", 
  364.       "Descriptor/parameter syntax error", 
  365.       "Unknown descriptor name", 
  366.       "Unknown parameter name", 
  367.       "A (or another) parameter name was expected but is "
  368.           "missing", 
  369.       "Unable to read parameter value(s) for this "
  370.           "descriptor", 
  371.       "Missing right parenthesis while reading units", 
  372.       "Too many (or duplicate) parameters given for this "
  373.           "descriptor", 
  374.       "Missing brackets around array data"}; 
  375.  
  376. #else 
  377.  
  378. extern char mel_err_msg[MEL_MAX_NUM_ERR_MESSAGES]
  379.                 [MEL_MAX_ERR_MSG_LEN+1]; 
  380.  
  381. #endif /* MEL_INIT */
  382.  
  383.  
  384. About The Author:
  385.  
  386. George M. Crews received his bachelors in General Engineering from the University of Nevada at Las Vegas, and his masters in Engineering Science from the University of Tennessee at Knoxville.  He is a "generalist" with over 15 years experience in mechanical and software engineering design and analysis.  He may be contacted at 109 Ashland Lane, Oak Ridge, TN 37830. (615) 481-0414.
  387.