[SparForte][Banner]
[Top Main Menu] Intro | Tutorials | Reference | Packages | Examples | Contributors   [Back Page]      [Next Page]  

Making New Bindings for Built-in Packages

This is a quick overview on adding new built-in packages to SparForte.  We'll use the Ada.Calendar.Year package as an example binding.

In the current version of SparForte, packages are hard-coded into the parser.  There's no ability to create separate files that can be loaded on demand.  Some day this ability may be added.

1. Install The Source Code (If Needed)

If you're adding a third-party project (like AdaCGI), create a subdirectory to hold the project and modify the main SparForte makefile to compile (and make clean) the project.  Test your changes by making a clean rebuild of SparForte.

2. Create a Parser Package

Create a new "parser_" package for binding. You may want to copy one of the existing ones and edit it rather than starting one from scratch.

Call your package startup/shutdown procedure in the scanner package.

3. Declare Your Identifiers

All identifiers are declared as variables. In your parser_*.ads package, declare an indentifier for your SparForte subprogram. Using Ada.Calendar.Year as an example, create

calendar_year_t : identifier; -- Ada.Calendar.Year

The "_t" suffix stands for "token".

Add declaration calls in your Startup procedure to declare the identifier variables (that is, add them to the symbol table and identify what types they are).  Copy some of the other declarations that are similar to the ones you are doing.  For example, to declare Ada.Calendar's time type,

declareIdent( cal_time_t, "calendar.time", variable_t, typeClass );

There are several declare calls.  declareIdent is a general purpose declaration that uses the identifier variable, the name of the identifier (as the user would type it), what root type it's derived from (variable_t is used for private types, integer_t for integers, string_t for strings, and so forth), and the class of identifier (typeClass for a type declaration, subClass for a subtype declaration, and so forth).

procedure StartupCalendar is
begin
  declareIdent( cal_time_t, "calendar.time", variable_t, typeClass );
  declareIdent( cal_year_number_t, "calendar.year_number", integer_t,
    typeClass );
  declareIdent( cal_month_number_t, "calendar.month_number", integer_t,
    typeClass );
  declareIdent( cal_day_number_t, "calendar.day_number", integer_t,
    typeClass );
  declareIdent( cal_day_duration_t, "calendar.day_duration", duration_t,
    typeClass );

  declareFunction( cal_clock_t, "calendar.clock" );
  declareFunction( cal_year_t, "calendar.year" );
  declareFunction( cal_month_t, "calendar.month" );
  declareFunction( cal_day_t, "calendar.day" );
  declareFunction( cal_seconds_t, "calendar.seconds" );
  declareProcedure( cal_split_t, "calendar.split" );
  declareFunction( cal_time_of_t, "calendar.time_of" );
  declareFunction( cal_to_julian_t, "calendar.to_julian" );
  declareFunction( cal_to_time_t, "calendar.to_time" );
  declareFunction( cal_day_of_week_t, "calendar.day_of_week" );
end StartupCalendar;

Recompile SparForte and check for errors.

4. Add Empty Bindings

The parser_ package specification should contain a series of "Parse" procedures.  These will be called by SparForte when it needs to run subprograms in the built-in package.  If a parse procedure defines a function, it should have one out unbounded_string parameter (to return the result of the function).

Procedures, which return nothing, have no parameters.  For the Ada.Calendar package, you might have:

procedure ParseCalClock( result : out unbounded_string );  -- Ada.Calendar.Clock function
procedure ParseCalYear( result : out unbounded_string );  -- Ada.Calendar.Year function
procedure ParseCalMonth( result : out unbounded_string ); -- Ada.Calendar.Month function
procedure ParseCalSplit; -- Ada.Calendar.Split procedure

For each of these procedures, follow them with a stub pragma

procedure ParseCalClock( result : out unbounded_string ); -- Ada.Calendar.Clock function
pragma import( stubbed, ParseCalClock );

This GCC Ada pragma indicates that ParseCalClock has not been completed and it will raise a PROGRAM_ERROR exception if the procedure is called.  When you complete the ParseCalClock procedure, remove the stub pragma.

Compile the package specification with gcc -c to make sure there are no obvious errors.

Now tie your package specification into the parser.  Edit the parser.adb file and add the name of your package with the "with" and "use" statements at the top of the file.

Get SparForte to take action when it sees a procedure or function.  (This is the purpose of those identifier variables you declared earlier.)  SparForte checks for built-in package procedures in ParseGeneralStatement.  It checks for built-in package functions in ParseFactor.

For an Ada.Calendar package, add the check for the Ada.Calendar.Split procedure in ParseGeneralStatement:

elsif token = cal_split_t then -- are we looking at "calendar.split"?
   ParseCalSplit;             -- then process a calendar.split call

Add functions like Ada.Calendar.Clock to ParseFactor.  The parameter is always "f" (the value of the "factor" and the variable "kind" must be set to the identifier variable for the type of result (e.g. an integer result has a kind of integer_t).

elsif token = cal_clock_t then -- are we looking at "calendar.clock"?
  ParseCalClock( f );
  kind := cal_tile_t;

Recompile SparForte to check for errors.  Try using the procedures and functions.  Each should raise a PROGRAM_ERROR exception but should have no other errors.

5. Run Your Bindings

The only thing left to do is to check the parameters to the subprograms and execute them.  Create a package body file and being implementing the Parse procedures one at a time.  As you implement each, remove the corresponding stub pragma from the specification file.

The variable "token" represents the current item in the source file.  To move to the next item in the source file, use the expect procedure.  Typically, you are only looking for an identifier or a punctuation mark.  For example,

   expect( cal_clock_t ); -- expect the identifier "calendar.clock"
   expect( symbol_t, "(" ); -- expect the punctuation mark "("

The parser has some Parsing functions that automatically process and report errors.  An important ones are:

   ParseExpessions( val, kind ); -- interpret any kind of expression.  Return the value and the type.

   ParseIdentifier( id ); -- interpret an identifier.   Return the id number for the identifier.

   ParseOutParameter( id, kind ); -- interpret an identifier.   Return the id number of the identifier. If it doesn't exist, it will be declared as a kind variable (if auto-declarations are allowed by the user).

   ParseInOutParameter( id, kind ); -- interpret an identifier.   Return the id number of the identifier.

Using expect and ParseExpression you can read through the parameters for most functions.  For example, to Ada.Calendar.Year has one parameter:

  year_value : unbounded_string;
  year_type : identifier;
begin
  expect( cal_year_t );
  expect( symbol_t, "(" );
  ParseExpression( year_value, year_type );
  expect( symbol_t( ")" );

Don't check for a semi-colon.  SparForte will do that later.

Now add the type checks.

The scanner has several functions to check the type of an identifier.  The main procedure is baseTypesOK.  This compares two type identifiers and verifies they are compatible with one another.  You don't have to report the error: baseTypesOK will do this for you.  baseTypes also handles derrived types and subtypes.

expect( cal_year_t );
expect( symbol_t, "(" );
ParseExpression( year_value, year_type );
if baseTypesOK( year_type, cal_time_t ) then  -- year should be a calendar.time type or compatible
   null;                                      -- do nothing special if type is OK
end if;
expect( symbol_t( ")" );

Recompile SparForte again and check your work.  Although calendar.year does nothing yet, SparForte should understand the parameters.  Using an integer or a character parameter instead of a calendar.time parameter should cause an error.  Leaving out a "(" or ")" should also cause an error.  Check your definition of types using the env command

=> env calendar.time
 ( private type )
=> env calendar.year
; -- built-in function

Finally, you need to actually execute the subprogram.  Before you execute anything, check to see if you should execute the function with the SparForte isExeuctingCommand function.  If SparForte is doing a syntax check of a script, or if an error was previously encountered, isExecutingCommand will be false.

It is also a good idea to wrap the function or procedure you are calling in an Ada declare block to catch and report any exceptions.  Otherwise, SparForte will crash because of the exception.

All the parameters are unbounded strings and will have to be converted to the appropriate Ada type needed for the parameters.  In the case of Ada.Calendar.Year,

if isExecutingCommand then
   begin
      result := to_unbounded_string( year( time( to_numeric( year_value ) ) )'img );
   exception when others =>
      err( "exception raised" );
   end;
end if;

An explaination of the conversions:

  • to_numeric - convert the unbounded_string year_value to a numeric type (in particular, a long_float)
  • time() - convert the long float year_value to a calendar.time type
  • year() - call the calendar.year function
  • 'img - convert the numeric result of calendar.year to a basic Ada fixed string
  • to_unbounded_string - convert the fixed string result to an unbounded_string to be returned to ParseFactor

The complete calendar.year function looks like this:

procedure ParseCalYear( result : out unbounded_string ) is
  expr_val  : unbounded_string;
  expr_type : identifier;
begin
  expect( cal_year_t );
  expect( symbol_t, "(" );
  ParseExpression( expr_val, expr_type );
  if baseTypesOK( expr_type, cal_time_t ) then
     null;
  end if;
  expect( symbol_t, ")" );
  if isExecutingCommand then
     begin
       result := to_unbounded_string( year( time( to_numeric( expr_val ) ) )'img );
     exception when others =>
       err( "exception raised" );
     end;
  end if;
end ParseCalYear;

Rebuild SparForte and test the function to make sure it works.  You've completed the implementation of "calendar.year".  Complete and test the rest of the bindings.

The actual ParseCalYear function is in the parser_cal.adb file.  The only difference to the calendar package compared to what you did here was that Ada.Calendar.Time was a private type so SparForte implements its own calendar package using a normal type so that time values can be converted to strings.

 
[Right Submenu]

 Vision

 Roadmap

 Source Guide

 UNIX Ports

 Window Ports

 Bindings

[Back to Top] Back To Top [Small Forte Symbol]