Interfacing with C#

Note: the interface of the generated C# code described here is already outdated and will be overhauled soon. If you want to get an idea of what this is going to be like a few months from now, have a look at the Java version.

When you compile a Cell program, the compiler does not generate a binary executable. Instead, it generates a text file containing code in the chosen output language. We'll examine the C# code generator here. If you define a Main(..) procedure in you Cell code, the generated code can then be handed over to a C# compiler to generate an executable file. That's how for instance the Cell compiler itself is built, and also the simplest way to build a program that tests your automata. But if you don't define a Main(..) procedure, the compiler will just generate a set of classes, one for each type of automaton in your Cell code, that can be used to instantiate and manipulate the corresponding automata from your C# code. The compiler will produce two files, generated.cs and interfaces.txt. The former contains the implementation of the classes generated by the compiler, while the latter documents their interfaces in pseudo-C# code. In this chapter we'll go through the interface of the generated classes and explain how to use them, and what each of their methods is for.

When using C++ as a target language, it is recommended that you don't use the generated classes directly, but create a wrapper class for each of them, and use those instead. With C# the interface of the generated classes is much nicer, and creating wrapper classes is probably not necessary. A better idea is probably to derive your own classes from the generated ones, and add new methods to them. Even though the compiler does its best to map the Cell types to native C# types, there are times when you'll need to manually convert the data that is exchanged between the two languages, and you really don't want to repeatedly perform those conversions all over your codebase: it's much better to have all of them in just one place. If a method of the generated classes requires some manual data conversion, the best thing to do is probably to define an overloaded version of the same method, or a similar one, in the derived class, and have it take care of all data conversions before and/or after invoking the generated one.

Data conversion

The most annoying and time-consuming part of using the classes generated by the Cell compiler from your own C# code is of course converting data back and forth between the C# and Cell representations. There's a number of simple Cell data types that are mapped directly to a corresponding C# type. They are shown in the following table:

Cell C#
Int long
Float double
Bool bool
Symbol string
String string
(T1, T2, ...) Tuple<T1', T2', ...>
T* T'[]
[T] T'[]
[T1, T2] Tuple<T1', T2'>[]
[T1, T2, T3] Tuple<T1', T2', T3'>[]
any_tag(T) T'

The above table should be mostly self-explanatory, but there's a couple things that need explaining:

  • Symbols are mapped to strings containing their textual representation. For example, nothing is mapped to the C# string "nothing".
  • Tagged types can be mapped directly to a C# type only if the tag is a known symbol and the type of the untagged value in turn has a direct mapping to a C# type. In this case, the tag is simply ignored and the mapping of the untagged value is used. A type like <person_id(Nat)>, for example, will be mapped to a long in C#, and the generated code will take care of adding or removing the person_id tag as needed.
  • The current mapping is not yet taking advantage of one important feature of C#, named tuples, which can be used to map Cell records. That will be fixed soon.

When dealing with more complex data types that are not in the above table, the mapping depends on the direction data is moving in. When passing data from C# to Cell you're expected to pass a string that contains the textual representation of a Cell value. That's neither elegant nor particularly efficient, but at least it's simple and straightforward. When data moves in the other direction, from Cell to C#, it is returned as an object of type CellLang.Value. Its declaration is shown here:

namespace CellLang {
  public interface Value {
    bool IsSymb();
    bool IsInt();
    bool IsFloat();
    bool IsSeq();
    bool IsSet();
    bool IsBinRel();
    bool IsTernRel();
    bool IsTagged();

    bool IsString();
    bool IsRecord();

    string AsSymb();
    long   AsLong();
    double AsDouble();

    string AsString();

    int Size();
    Value Item(int index);
    void Entry(int index, out Value arg1, out Value arg2);
    void Entry(int index, out Value arg1, out Value arg2, out Value arg3);

    String Tag();
    Value Untagged();

    Value Lookup(string field);

    void Print(TextWriter writer);
  };
}

This interface is implemented by a number of concrete classes each of which is used to represent a particular type of Cell value: symbols, integers, floating point numbers, sequences, sets, binary and ternary relations and tagged values. These concrete classes are hidden from the user, and they can be manipulated only through their common "fat" interface, whose methods can be divided into three groups. The first one comprises all the bool Is*() methods, which are used to discover the type of the value represented by the target object. Then there's a group of methods that are used to actually access the data held by those objects:


  // Defined only for symbols. Returns a string that
  // contains the textual representation of the symbol
  string AsSymb();

  // Defined only for integers
  // Returns the value as a 64-bit signed integer
  long AsLong();

  // Defined only for floating point numbers
  // Returns the value as a double precision floating point number
  double AsFloat();

  // Defined only for strings
  string AsString();

  // Defined for all collection types:
  // sequences, sets, binary and ternary relations
  int Size();

  // Defined only for sequences and sets
  // Returns the i-th value in the collection
  // In the case of sets, elements are arranged
  // in an implementation-defined order
  Value Item(int index);

  // Defined only for binary relations
  // Returns the i-th pair in the relation
  // Pairs are arranged in an implementation-defined order
  void Entry(int index, out Value arg1, out Value arg2);

  // Defined only for ternary relations
  // Returns the i-th triple in the relation
  // Entries are arranged in an implementation-defined order
  void Entry(int index, out Value arg1, out Value arg2; out, Value arg3);

  // Defined only for tagged values
  // Returns the textual representation of the tag
  string Tag();

  // Defined only for tagged values
  // Returns the value without the tag
  Value Untagged();

  // Defined only for records
  // Returns the value of the corresponding field
  // The only argument is the textual representation of the
  // field symbol, e.g. point.lookup("x")
  Value Lookup(string field);

Each of these methods is actually implemented only in some of the classes that can hide behind the Value interface, and if used with the wrong concrete class will just throw an exception. long AsLong(), for example, can only be used if the target object actually holds an integer value, which can be checked using the bool IsInt() query method.

The last method, void Print(TextWriter writer), is used to generate the textual representation of the value, which is written to the provided TextWriter object.

Relational automata

Let's take a look at the interface of the classes produced by the compilation of a relational automaton. We'll start with a very simple one, Counter:

class CellLang.Generated.Counter {
  Counter();

  Value ReadState();
  void SetState(string newState);
  void Execute(string message);

  long Value;
  long Updates;

  long NewValue(string msg);
}

As you can see, the generated C# class has the same name of the Cell automaton it derives from, and is nested inside the Generated static class of the CellLang namespace. The first three methods, ReadState(), SetState(..) and Execute(..), are the same for all relational automata. All other methods are just accessors that are specific to a particular automaton, and can be used to read pieces of its state, or to invoke its methods.

The ReadState() method is the equivalent of the read instruction in Cell: it takes a snapshot of the state of the automaton and returns it as a CellLang.Value object. Saving the state of an automaton to a file in text form can be done with the instruction instance.ReadState().Print(tw);, where tw is a valid instance of TextWriter.

SetState(..) is used to set the state of an automaton instance, and is the equivalent of the write instruction in Cell. It can be used at any time in the life of the automaton instance, any number of times. The new state has to be provided in text form. Here's an example:

counter.SetState("(value: -10, updates: 0)");

If the provided state is not a valid one, SetState(..) will throw an exception. In that case, the automaton instance will just retain the state it had before, and will still be perfectly functional.

Execute(..) is used to send the automaton a message, which has to be passed in text form. A few examples:

counter.Execute("incr");
counter.Execute("decr");
counter.Execute("reset");
counter.Execute("reset(-1)");

Errors handling works in the same way as with SetState(). If an error occurs an exception will be thrown, but the automaton will remain fully operational, and its state will be left untouched.

The two properties Value and Updates are Counter-specific accessors that return the values of the value and updates member variables respectively. Note that the types of such variables is just Int, which can be mapped directly to long in C#, with no need to use CellLang.Value.

Let's now take a look at a more complex automaton, Supply. Here's the declaration of the generated C# class:

class CellLang.Generated.Supply {
  Supply();

  Value ReadState();
  void SetState(string);
  void Execute(string);

  long NextPartId;
  long NextSupplierId;

  bool Part(long);
  long[] Part();

  bool Supplier(long);
  long[] Supplier();

  bool Code(long, string);
  Tuple<long, string>[] Code();

  bool Phone(long, string);
  Tuple<long, string>[] Phone();

  bool Sells(long, long);
  Tuple<long, long>[] Sells();

  bool Availability(long, long, long);
  Tuple<long, long, long>[] Availability();

  bool UnitPrice(long, long, long);
  Tuple<long, long, long>[] UnitPrice();

  bool Name(long, string);
  string Name(long);
  Tuple<long, string>[] Name();

  bool Address(long, string);
  string Address(long);
  Tuple<long, string>[] Address();

  bool Description(long, string);
  string Description(long);
  Tuple<long, string>[] Description();

  Value LowestPriceSuppliers();
  long[] LowestPriceSuppliers(long);
}

The first three methods of the Supply class, ReadState(), SetState(..) and Execute(..), are the same as before. The two properties, NextPartId and NextSupplierId, are just accessors for the corresponding member variables, just like Value and Updates in Counter. All the other methods are new, and are either accessors for some mutable relation variable or wrappers for a Cell method. A first set of methods, of the form bool In*(..), checks whether a relation contains a given tuple (or value, for unary relations):

// Checks whether the part unary relation
// contains the value part_id(1)
supplyInstance.Part(1)

// Checks whether the sells binary relation contains
// the pair supplier_id(8), part_id(2)
supplyInstance.Sells(8, 2)

// Checks whether availability contains the
// triple supplier_id(7), part_id(3), 25
supplyInstance.Availability(7, 3, 25)

A second group of methods, like Supplier() or UnitPrice(), return the entire content of a given relations, as an array of tuples, or just an array of values in the case of unary relations.

Binary relations with a key on the first column (that is, maps) also have accessors that return the value corresponding to a given key, provided that the relation/map contains such key:

// Looks up the name of the supplier identified by the value
// supplier_id(21) if the name relation contains an entry
// for such supplier. Throws an exception otherwise
string name = supplyInstance.Name(21);

The last group of methods (LowestPriceSuppliers(..)) are just the compiled C# version of the corresponding Cell methods of the Supply automaton.

Reactive automata

We'll use Switch as our first example. This is the interface of the corresponding generated class:

class CellLang.Generated.Switch {
  enum Input {SWITCH_OFF, SWITCH_ON};

  enum Output {IS_ON};

  Switch();

  void SetInput(Input input, string value);
  Value ReadOutput(Output output);

  void Apply();
  Value ReadState();
  void SetState(string newState);

  Output[] ChangedOutputs;

  // Inputs
  bool SwitchOff;
  bool SwitchOn;

  // Outputs
  bool IsOn;
};

The first thing to note here is the two enumerations Input and Output, whose elements are the uppercase version of the names of the inputs and outputs of Switch. These are used in conjunction with the methods SetInput() and ReadOutput() as shown here:

// Setting the value of the two inputs
switchInstance.SetInput(Switch.SWITCH_ON, "true");
switchInstance.SetInput(Switch.SWITCH_OFF, "false");

// Propagating the changes to the inputs
// throughout the automaton instance
switchInstance.Apply();

// Reading and printing the value of the only output
Value isOn = switchInstance.ReadOutput(Switch.IS_ON);
isOn.Print(Console.Out);

As an alternative to SetInput(..) and ReadOutput(..), which can operate on any input or output and use the textual representation of a value or CellLang.Value respectively as an exchange format, the generated class also provides another set of methods each of which can manipulate a single input or output, but that are more convenient to use in most cases. The above code snippet can be rewritten as follow:

// Setting the value of the two inputs
switchInstance.SwitchOn = true;
switchInstance.SwitchOff = false;

// Propagating the changes to the inputs
// throughout the automaton instance
switchInstance.Apply();

// Reading and printing the value of the only output
bool isOn = switchInstance.IsOn;
Console.WriteLine("{0}", isOn);

The ReadState() and SetState(..) methods work in the same way as with relational automata, but with the limitations we've already discussed for time-aware automata. The property ChangedOutputs provide you with a list of outputs that have changed (or have been active, in the case of discrete outputs) as a result of the last call to Apply():

// Changing inputs here
...

// Propagating those changes
switchInstance.Apply();

// Iterating through the outputs that have changed
// if continuous or have been activated if discrete
foreach (var outputId in switchInstance.ChangedOutputs) {
  // Reading the value of the changed output
  Value outputValue = switchInstance.ReadOutput(outputId);

  // Now time to do something with the value of the output
  ...
}

The last thing we need to see is how to deal with time-aware automata. We'll use WaterSensor:

class CellLang.Generated.WaterSensor {
  enum Input {RAW_READING};

  enum Output {SENSOR_STATE};

  WaterSensor();

  void SetInput(Input input, string value);
  Value ReadOutput(Output output);

  void SetElapsedMillisecs(uint);
  void SetElapsedSecs(uint);

  bool Apply();
  Value ReadState();
  void SetState(string);

  Output[] ChangedOutputs;

  // Inputs
  string RawReading;

  // Outputs
  Value SensorState;
}

The only differences here, apart from the input setters and output getters which are obviously specific to each automaton type, are the two extra methods SetElapsedSecs(..) and SetElapsedMillisecs(..) and the fact that Apply() now returns a boolean value. The former are the equivalent of the elapsed instruction in Cell, and the value now returned by Apply() has the same meaning as the one returned by the apply instruction in a Cell procedure. Here's an example of how to update an instance of WaterSensor:

// Updating the values of the inputs here
...

// Setting the amount of time that has elapsed
// since the last call to waterSensor.Apply()
waterSensor.SetElapsedMillisecs(100);

do {
  // Repeatedly calling Apply() until it returns true
  // That happens only once all pending timers have
  // been processed and the changes in the values of
  // the inputs propagated throughout the automaton
  bool done = waterSensor.Apply();

  // Iterating through the outputs that have changed
  // if countinuous or have been activated if discrete
  foreach (var outputId in waterSensor.ChangedOutputs) {
    // Reading the value of the changed output
    Value outputValue = waterSensor.ReadOutput(outputId);

    // Now time to do something with the value of the output
    ...
  }
} while (!done);