Platform Interaction

PIL's built-in types are rarely enough to generate a fully operational library or application. Very often you need access to platform libraries and frameworks. An example of this is database access. If you generate code that has to take advantage of a database, i.e. has to execute queries and retrieve their results, this can be achieved in PIL using external classes.

In order to do this you first have to come up with a unified interface to this functionality. In the case of database access you essentially need 3 things: a notion of a Database, the notion of a connection to that database and a result set. For each of these we have to identify the methods and fields we need. We then define the result using external class definitions. For instance:

    external class pil::db::Database {
      new(String hostName, String username, String password, String database);
      pil::db::Connection getConnection();
    }

    external class pil::db::Connection {
      List<Result> query(String query, Array<Object> args);
      void updateQuery(String query, Array<Object> args);
      void close();
    }

    external class pil::db::Result {
      Int getInt(Int index);
      String getString(Int index);
      Object getObject(Int index);
    }

These definitions tell the PIL typechecker that it can assume each platform will have an implementation of a the specified classes.

Within the rest of the PIL code, instances of these classes can be created and used like any other PIL object. After generating the platform code for the PIL program, the generated code has to be combined with or linked against platform-specific implementations for each of these external definitions. These implementations typically wrap an existing API (such as JDBC on Java, in this case) and have to have exactly the same behavior on each platform

An implementation of the Connection class in Java, for instance, could look like this:

    package pil.db;

    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.ArrayList;
    import pil.db.dialect.Dialect;

    public class Connection {
        private java.sql.Connection conn;
        public Connection(java.sql.Connection conn) {
            this.conn = conn;
        }
        public ArrayList<Result> query(String sql, Object[] args) {
            try {
                PreparedStatement stmt = conn.prepareStatement(sql);
                for(int i = 0; i < args.length; i++) {
                    stmt.setObject(i+1, args[i]);
                }
                ResultSet rs = stmt.executeQuery();
                ArrayList<Result> results = new ArrayList<Result>();
                while(rs.next()) {
                    results.add(new Result(rs));
                }
                return results;
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        public void updateQuery(String sql, Object[] args) {
            try {
                PreparedStatement stmt = conn.prepareStatement(sql);
                for(int i = 0; i < args.length; i++) {
                    stmt.setObject(i+1, args[i]);
                }
                stmt.executeUpdate();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

      public void close() {
        try {
          conn.close();
        } catch(Exception e) {
        }
      }
    }

A few things to note:

  • We catch checked exceptions everywhere and rethrow them as RuntimeExceptions, because PIL has no notion of checked exceptions.