Automated Interface Code Generation from Ada Specification s Paul R. Pukite DAINA, 4111 Central Ave . NE Columbia Heights, MN 5542 1 (612)781-760 0
[email protected] .edu ABSTRACT: Mapping data structures between incompatible representations remains a n error-prone and tedious part of program development . This letter outlines a method fo r automating interface code generation based on Ada package specifications and the use o f Ada keywords as interface mapping directives . This concept supports information hiding b y placing the details of the generated interface code in the package body .
Introduction For many application programmers, creating code which interfaces incompatibl e sections of software remains a tedious part of the software development process . Often, automated methods have reduced the labor required, especially for tasks such a s development of a graphical user interface or a database interface . Common interfac e development tools in use include graphical programming environments and resourc e compilers. However, in less conventional applications or where no standards exis t [Voketaitis 92], programmers must often depend on their ingenuity and the language , compiler, and operating system constructs available to generate the necessary interfac e code. This letter focuses on the automatic generation of Ada code for interfaces o f specific functionality ; namely those that provide representational control and mappin g between Ada data types and another (perhaps foreign) data type format . In particular, specific systems chosen to demonstrate the utility of the approach include an Ada application program interface to a peripherally attached digital signal processor (DSP ) and an Ada interface to a Prolog interpreter .
Problem Definition The underlying structural model considered is a one-to-one mapping fro m heavily-typed Ada data objects to untyped or weakly-typed foreign entities (the interfac e construction details will of course differ among applications) . In addition, the mappin g could include the transfer of data along a message path or data bus . Note that thi s definition of interface is more general than the mapping between Ada and assembl y language or C code . For these more common applications, Ada compiler pragma's ar e used to handle the conversion invisibly . In this respect, our definition o f representational control refers to the mapping between data types (inter- or intraapplication) which are not supported by the compiler or operating system .
ACM Ada Letters, May/Jun, 1993
Page 74
Volume XIII, Number 3
Sourc e System Obj_Link
Ob j
IO .Source_Link ;
IO .Connect Link ID Source Addr Source:Size
=> => => =>
Targe t Syste m Obj_Link : IO .Target_Link ;
Obj_Link, IO .IDs'FIRST, Obj'ADDRESS Obj'SIZE ) ;
TO .Connect ( Link ID Target_Addr Target_Size
=> => => =>
Obj_Link , IO .IDs'FIRST , Obj'ADDRES S Obj'SIZE ) ;
IO .Transfer(Channel => Obj_Link) ;
Figure 1 . Linear mapping of messages, where source and target are identical in structure . An example of a simple form of interface mapping that does not require much coding effort is the sending of fixed format messages through an I/O channel (bus, seria l port, etc.) . Given that the source and destination objects have the same size an d format, the transfer of the source to the destination can use the protocol shown i n Figure 1 . The connection information is stored in the privately defined Link parameter to be used by the I/O routines located in each system . The identification number is used for routing the message . This method works satisfactorily as the strictly enforced typ e checking of Ada can be bypassed through Unchecked_Conversion or an assemble r routine . A generic packaging into a connection object would also simplify the fina l implementation . More detail on this linear mapping is given in [Campbell 92] . The problem with extending this approach to a more general mapping is that th e objects themselves may be differently defined and/or sized . For example, th e Source_System .Obj may be an Ada defined STRING type while the Target_System .Ob j may be a character buffer with a required ASCII NUL termination . Extra code woul d then be required to intelligently parse the Obj specifications into the foreign forma t before transferring the Obj contents. Note that the use of Ada representational clause s alone will be insufficient in many similar situations . An automatic code generator woul d aid in this respect by determining which components transferred are strings, integers , reals, etc ., thus eliminating any manual intervention . The table in Figure 2 gives examples of mappings that would require such an intelligent transfer of object contents. Based on this general definition and the language constructs available, Ad a should be well-suited for specifying representational control . In fact, the key in creatin g an automatic interface code generator (ICG) through Ada is to use packag e specifications to define the mapping protocol . Subsequently, a YACC or other parser generator tool can lexically analyze, parse, and process the specification to the fina l ACM Ada Letters, May/Jun, 1993
Page 75
Volume Xlll, Number 3
Ada type
Target
Mapping
Character
Word
Target uses a 16-bit little-endian word to represent a character, whereas the Ada source is 8-bit big-endian .
String
Pointer
Copy a string to a buffer location in the Target . Terminate with an ASCII NUL.
Float
Float
Source is 64-bits, Target is 32-bits in a non-IEEE floatin g point format .
Enumeration, String , Integer, Float, etc .
Symbol
Target uses a tagged symbolic format to represent types . Interface must recognize the Ada type and call th e appropriate conversion routine . Symbols can have the same ASCII text representation as the Ada object names .
Record
Record, Structure, or Functor
Each component of the record must be mapped to th e corresponding component in the Target . The component s could be any of the above types .
Array
List
Each component must be added to a list in the Target .
Figure 2 . Examples of specialized mapping protocols . representation . In this way, the CASE tool is embedded in the Ada specification cod e itself, and does not need other documentation (such as an IDL representatio n [Snodgrass 1989]) . As a starting point, the ICG uses the Ada keywords package, procedure, an d function to establish the overall mapping encapsulation and data transfer protocols .
Figure 3 briefly describes the meaning of each keyword . From there, objects and type s that need transformation can be specified . In the following, two applications are presented that demonstrate the propose d code generation methods more concretely . In the last section, the generator design i s described in detail . Example 1. PC-Ada to DSP-C mapping. In this example, a control program is designed in Ada to run on a host PC o r workstation . The system requirements specify an attached DSP to preprocess the dat a provided by an external source . The processed data is then transferred to the host computer from the DSP . In short, the representational control needed is to bilaterall y transfer the data from the DSP working area to the host memory . The data formats used by the DSP [Fuccio 88] include 32 bit floats (in a non ACM Ada Letters, May/Jun, 1 .9.93
Page 76
Volume XI/I, Number 3
Ada keyword
Definition
Mappin g
Package
Interface encapsulation
Package body contains the interface code which i s hidden from the implementor .
Procedure
Query or twoway transfer
Target uses the procedure name as an identifier, accepts data through IN or IN OUT parameters an d returns through the OUT or IN OUT parameters .
Function
Assertion o r storage
Target uses the function name as an identifier, and stores data supplied through IN parameters .
Figure 3. Ada keywords for interface mapping . IEEE standard format) and 16 bit integers. The DSP software is written in non-ANS I C, and compiled with a DSP cross compiler . The challenge is to control the DSP vi a the host Ada program and pass the processed data from DSP to host via a direc t memory access (DMA) protocol - with a limited amount of manual coding . package DSP_I0 i s Max_Data : constant
100 ;
type Data_Array is array ( 1
. . Max_Data ) of FLOAT ;
procedure Get_Data ( Mum Avg : in INTEGER ; Data_Out out Data_Array ) ; end DSP_I0 ;
Figure 4. Specification for Ada/DSP interface . Consider first an Ada package specification for the DSP-to-host transfer a s shown in Figure 4. This package defines a data type Data_Array and a data handle r Get_Data which taken together specifies an abstract interface between the host Ad a program and the peripherally attached processor . The package body to be automaticall y generated supplies the necessary logical translation between the processors . To outline the code generation steps, the DSP_IO specification is first lexicall y analyzed and parsed for interface procedures (any procedure defined within th e specification, such as Get_Data) . Then the base types and directions (IN, OUT, or I N OUT) of the data parameters are determined . In this example, the Num_Avg object is identified as a parameter to be sent to the DSP as a moving average parameter, whil e the Data_Out array will be retrieved from the DSP . Next, the necessary code for th e parameter downloading and uploading is generated by the ICG . ACM Ada Letters, May/Jun, 1993
Page 77
Volume Xlll, Number 3
It is important to note that since the DSP's data areas can be accessed b y symbolic names instead of address, the specification can be simplified by using the sam e naming conventions for the Ada objects . For example, as the DSP executive's symbol table uses the names Num_Avg or Data Array to refer to the respective memory locations, we can use the same names in the Ada specification (remembering capitalization) . During operation, the generator code processes the name, does a lookup in the symbol table for the DSP address, and then uses the address to write o r read from the DSP's memory area. declar e Buffer : DSP o IO .Data Array ; begi n DSP_TO .Get_Data { Num_Avg => 5 , Data_Out => Buffer ) ;
Figure 5. Code fragment calling DSP interface procedure . For this application, a C-language procedure is also needed within the DS P program to run a specific data gathering algorithm using the Num_Avg parameter an d returning the Data_Array buffer. By concurrently generating a skeleton for the foreig n Get_Data procedure, code becomes more readable and maintainable across the syste m boundaries . The code fragment in Figure 5 is a typical call to the Get_Data procedure . Appendix A gives the automatically generated Ada package body that does th e representational translation for this call . Note that the interface procedure follows th e pattern of downloading data, starting the remote algorithm, waiting for the result, an d then uploading data to the host . The package body uses DSP and DMA library routines located in the DSP_Utilities package, which remains hidden from the implementor . The generated C-interface skeleton for accessing the DSP data gatherin g procedure is also shown in the inset . Example 2. Ada to Expert System mapping . Next, an Ada application program that interfaces to expert system shell softwar e is illustrated . The expert system is based on a Prolog-style inference engine an d interpreter (which is constructed from portable Ada [Pukite 92]) . The expert system allows parts of the application program to be interpreted at run-time . The connectio n between the application program and the shell can be either through a static Ada librar y link, a message interface, or a dynamic data exchange (DDE) interface. The representational control needed here is to supply the expert system with data (in th e form of facts) and a query, and then to translate the query result to a data type that ca n be used by the Ada application program . Similar issues occur when interfacing Prolo g to other languages [Boyd 90] . ACM Ada Letters, May/Jun, 1993
Page 78
Volume Xlll, Number 3
package Genealogy i s type Families is ( Cat, Bovine, Primate, Unknown ) ; Query_Error : exception ; procedure Categorize ( Observed : in Answer
STRING ; out Families ) ;
function Belongs_To ( Specie : STRING ; Family : Families ) return BOOLEAN ; end Genealogy ;
Figure 6 . Example of Ada to Prolog interface package specification .
Interpreted Prolog uses a symbolic untyped data format. Incoming symbols ar e recognized and stored internally as string, real, or integer tokens . Obviously, Ada doe s not have semantics for run-time binding built in to the language, thus requiring additional Ada translation code . The challenge here is to pass data between th e strongly-typed Ada application program and the symbolically driven expert system . The interface program must recognize the object being passed and do the bilatera l conversion between Ada objects and the Prolog symbols . Clearly, a way of representing assertions and queries in terms of simple Ada syntax would eliminate much of th e coding and maintenance requirements . % -- Prolog-style rulebase -- ASCII File "Genealog .Pro " belongs_to(lion, cat) . belongs_to(cow, bovine) . belongs to(monkey, primate) . categorize(Species, Family) :- belongs_to(Species, Family) . categorize(_, unknown) . % -- Prolog-style query -- Typical interactive inpu t categorize(Species,Family) ?
Figure 7 . Prolog-style ASCII rulebase and query .
An Ada package specification for a hypothetical Ada-to-Prolog mapping is show n in Figure 6 . This package uses a string input data type, an enumerated data typ e Families, a query handler Categorize, and a fact asserter Belongs_To to abstract an interface between the Ada application program and the Prolog-style rulebase . The goal here is to assert facts into the Prolog knowledgebase and then use the rules previousl y stored there to make inferences and deduce a response (a good reference for Prolo g programming is found in [O'Keefe 90]) . The run-time interpreted rulebase is shown in Figure 7 . ACM Ada Letters, May/Jun, 1993
Page 79
Volume Xlll, Number 3
The logical mapping from the Ada specification to Prolog-style syntax is shown i n Figure 8 . In addition, as the assertion of a fact should be clearly apparent within a program, the dummy procedure Assert (see Figure 9) is provided to signify this action . Ada Functio n
Prolog Fac t
function Belongs T o Specie STRING ; Family Familie s return BOOLEAN ;
-6
Ada Procedure
belongs t o lion , ca t )• Prolog Quer y
procedure Categoriz e Observed in STRING ; Answer : out Familie s ) ;
-b
categoriz e lion , FAMILY )?
Figure 8 . Mapping from Ada to Prolog .
Figure 9 . Fact assertion abstraction .
Once the package body is generated by the ICG, the code fragment in Figure 1 0 can be used as a typical assert and query test case . This is similar to a conventional database access ; after asserting the fact into the rulebase, applying the quer y Categorize allows us to retrieve the Family_Name answer . The fundamental role of the ICG in this example is to map the Ada defined dat a structures to the Prolog interpreted environment . This means that the Prolog functor s "belongs._to( )" and "categorize( )" as well as the symbol "cat" must be referred to b y their ASCII representation . The bookkeeping is done automatically by the interfac e generator. For example, the ICG converts the Ada specification function nam e Belongs_To to a functor "belongs_to( )", the procedure name Categorize to a functor "categorize( )", and the enumerated Cat to a symbol "cat" .
ACM Ada Letters, May/Jun, 19 .93
Page 80
Volume Xlll, Number 3
declar e Family_Name : Genealogy . Families ; begi n Load ( "Genealog .Pro" ) ; Assert ( Genealogy .Belongs_To ( "tiger", Genealogy .Cat ) ) ; Genealogy .Categorize ( "tiger", Family_Name ) ;
Figure 10 . Example code fragment for Ada to Prolog call .
Appendix B shows the package body that will do the representational translation . The body includes code for converting the enumerated data type Families to a symbolic or string format used by Prolog . Comparing to Example 1, note that the quer y procedure follows a similar pattern of downloading data and variables, starting th e Prolog interpreter, and then transferring the variable results when the query finishes . The Prolog interpreter and its library routines are accessed through the Rule_Processo r package, which is also hidden from the implementor . Note that because of Prolog's binding rules, IN OUT parameters are not allowe d in interface procedure specifications, with the OUT parameters representing Prolog variable (capitalized ASCII) symbols . During the Prolog resolution process, th e exception Query_Error defined in the package specification will be raised if a patter n match cannot be determined . This would occur if no names within the rulebase woul d match those of the procedure Categorize and its parameters (i .e. the arity).
Interface Generation Method s The automatic interface generators for the DSP and Prolog interfaces wer e written in Ada. The structures of the two generators is similar, with a common front end parser and specific back-end interface builders (see Figure 11) . Both interface generators use a lexical analyzer and YACC parser written in Ad a (the YACCA parser from Tampere University of Technology, Finland) as a front-end . The YACC grammar input is adapted from the full Ada grammar to handle only Ad a package specifications . This reduces the compiler load considerably . The back-end procedure or interface builder takes the YACCA output token s and generates the code that is specific to the application . Most of the algorithmic complexity at this stage comes from reducing the specification data types to their bas e types of INTEGER, FLOAT, CHARACTER, STRING, etc. This type resolution includes keepin g track of subtypes, record components, and array definitions . Once these data types ar e known, the builder generates code for calling library routines that do data ACM Ada Letters, May/Jun, 1993
Page 81
Volume Xlll, Number 3
transformations (including downloading and uploading), initialization, etc . From the examples in the appendices, note that predefined Ada attributes such as 'SIZE and operations such as array slices are important constructs for code generation . Once created, the generated code performs the application tasks listed in Figure 12 .
Data Flow Diagram for Interface Generato r Ad a Specification
YACCA Parser
-►
Interfac e Builde r
Ada Bod y
1
Library o f Interfac e Routines
(optional ) Foreig n Interfac e Skeleton
Figure 11 . Package bodies are generated by parsing Ada specifications . Then, library routines for data transformation are incorporated where needed .
To improve the interface generator capabilities, standardized access to the hos t compiler's library through an Ada semantics interface specification (ASIS) tool [STAR S 90] could reduce the parsing complexity . Also, if more than one generator is to b e built, the interface builder procedure could be supplied as a generic formal parameter . In specific cases, generic packages such as Enumeration 10 are also useful fo r converting between numerical and text representations (e .g . a generic instantiation coul d have been used in Appendix B instead of the Families_Get conversion function) .
Download Data IN Procedur e Specification
Functio n Specification
Proces s
Download Dat a Proces s
OU T IN OUT
Upload Dat a
Figure 12 . ICG subprograms perform basic interface translation tasks .
ACM Ada Letters, May/Jun, 1993
Page 82
Volume Xlll, Number 3
Summary and Conclusion s The method described in this letter is a declarative approach to generating interface code . This means that the package specification used to abstract an interfac e is sufficent to generate the code which performs the specific representatio n transformation . With the techniques described, the code generator places all require d interface code in the package body, using only the Ada specification and selected keywords as inputs. Moreover, this serves to hide the implementation details from th e rest of the world. From the example applications presented, we have found that Ada as a specification language is well suited for generating interface routines . This is perhap s not too surprising because the Ada language has powerful constructs for definin g parameter mode (IN, OUT, or IN OUT) and accessing attributes (e .g. 'LENGTH and 'SIZE) . The approach described in this letter can be used in many other applications . In general, if the representation transformation can be described in terms of an Ad a package specification, an ICG is probably capable of creating the correspondin g translation code. References [Boyd 90] J.L. Boyd and G.M. Karam, "Prolog in 'C', SIGPLAN Notices, vol.25-7, 1990, p .63. [Campbell 92] J.A. Campbell, "Creating Structure from Linearity in Non-Ad a Interfaces", Ada Letters, Jul/Aug 1992, p .20. [Fuccio 88] M .L. Fuccio et al, "The DSP32C : AT&T's Second-Generation Floating Point Digital Signal Processor", IEEE Micro, December 1988 . [O'Keefe 91] R .A. O'Keefe, The Craft of Prolog, (MIT Press, Cambridge, MA, 1990) . [Pukite 92] P .R. Pukite, J . Pukite, and D .S . Barnhart, "Expert System for Redundancy and Reconfiguration Management", Proceedings of the IEEE National Aerospac e and Electronics Conference, 1992, p .233. [Snodgrass 89] R. Snodgrass, The Interface Description Language, (Computer Scienc e Press, Rockville, 1989) . [STARS 90] "Software Technology for Adaptable, Reliable Systems", Nationa l Technical Information Service #AD A229-349, November 2, 1990. [Voketaitis 92] A .M. Voketaitis, Jr ., "A Portable and Reusable RDBMS Interface for Ada", Ada Letters, Sept/Oct 1992, p .64 . ACM Ada Letters, May/Jun, 1993
Page 83
Volume XI/1, Number 3
APPENDIX A : Generated package body for Ada-DSP interface Generated Package Body with dsp_utilities ; with system ; package body dsp_io i s package du renames dsp_utilities ; execs loaded : integer := 0 ; flag_dsp : du .unsigned ; num avg_dsp : du .unsigned ; data_out_dsp : du .unsigned ;
/* Generated DSP C code * / #include "dsp .h ' #define Max Data 10 0 int Num_Avg ; float Data Out(Max_Data] ; main( )
procedure init dsp i s temp : du .unsigned ; begi n temp du .default_addr ; -- Download the DSP executive . if not du .download exec ( "a .out", execs loaded) the n raise program_error ; end if ; du .run . execs loaded := 1 ; -- Get DSP physical addresses . flag_dsp du .get_addr("flag") ; num_avg_dsp := du .get_addr("Num A v data out dsp := du .get_addr("Data_U u end init_dsp ;
{ WaitUntilFlag() ; /*while(!flag)* / Get_Data(Num Avg, Data Out) ; ConvertlEEE(Max_Data Bata Out) ; ResetToStart() ; /*flag=0*/ ~ }
Get Data(Num_Avg, Data_Out ) int Num_Avg ; float Data_Out[] ; { /*insert processing code here* / }
f
Generated C-interface skeleton used for creating DSP executive " a .out" .
DSP data transfer procedure procedure get_data(num_avg : in integer ; data out : out data_array) i s start : integer := 1 ; begi n if execs loaded = 0 the n init cusp ; end if ; -- Download input parameters . du .download array(num avg dsp, (num_avg'size + 7)/8, num_avg'address) ; Start DSP program ny setting flag . du .download_array(flag dsp, (start'size + 7)/8, start'address) ; -- Wait for DSP to finish, allow other tasks to run . while du .dsp_run_flag(flag_dsp) loo p delay 0 .05 ; end loop ; -- Upload output parameters . du .upload_floats(data_out_dsp, (data_out'size + 7)/8, data_out'address) ; exceptio n when others = > du .halt ; raise ; end get_data ; end dsp_io ;
ACM Ada Letters, May/Jun, 1993
Page 84
Volume XI/1, Number 3
APPENDIX B : Generated package body for Ada-Prolog interfac e Generated Package Body with rule_processor ; package body genealogy i s package rp renames rule_processor ; type string access is access string ; -- Enumeration strings . families array : constant array(families) of string_access (unknown = > new string'("unknown"), primate => new string'("primate"), bovine => ne w string'("bovine"), cat => new string'("cat")) ; -- Enumeration conversion . function families_get(str : string ; last : integer) return families i s begi n for i in families loo p if str(1 . . last) = families_array(i) .all(1 . . last) the n return i ; end if ; end loop ; raise program error ; end families_get ; Prolog query procedure procedure categorize(observed : in string ; answer : out families) i s temp string : rp .symbol ; last : integer ; begi n rp .start fact_input(query => true) ; rp .input_functor("categorize") ; -- Download string . rp .input_symbol(observed) ; -- Download variable . rp .input_symbol("ANSWER") ; rp .end_fact input ; Run query . if not rp .interpret(token_input => true, do_tro => true) the n raise query_error ; end if ; rp .start token_get ; Upload enumeration . rp .get_symbol string(temp_string, last) ; answer := families_get(temp_string, last) ; end categorize ; Prolog database entry function belongs_to(specie : string ; family : families) return boolean i s begi n rp .start fact input ; rp .input`functor("belongs_to") ; -- Download string . rp .input_symbol(specie) ; -- Download enumeration . rp .input ` symbol(families_array(family) .all) ; rp .end_fact input ; -- Assert facts . return rp .interpret(token_input => true) ; end belongs_to ; end genealogy ; ACM Ada Letters, May/Jun, 1993
Page 85
Volume XI/I, Number 3