The #Smalltalk compiler doesn't have special messages for ifTrue:, whileFalse:, etc. Instead it uses macros. Macros are defined by rewrite rules from the Refactoring Browser. By defining an annotation with a key value of "Macro". The value of the annotation is a string that when parsed is a literal array. The first item in the literal array is the name of the macro, the second item is the search pattern of the rewrite rule and the last item is the replace pattern.

Macros are inherited. If you define a macro in a class, all subclasses also use the macro. However, you can remove the macro from a subclass by adding another Macro annotation with the same macro name and nil for the search and replace patterns.

The Smalltalk.sif file contains macros defined on Object to optimize several common messages (e.g., ifTrue:, whileTrue:, etc.). These macros transform the messages into the "Compiler primitive:" syntax.

Prerequisites

The SIF format doesn't define any standard way to specify prerequisites. In #Smalltalk you can define prerequisites using an annotation with a key of "Prerequisite" and a value that is the filename to be included. Whenever a prerequisite annotation is parsed, the file referenced by the prerequisite is immediately parsed. Once the prerequisite file is parsed, the remaining code in the original file is parsed. #Smalltalk searches for files in the current directory first, and if the file is not found, then it searches for it in the Source subdirectory where the sst.exe executable is located.

Typed Instance Variables

You can specify types for instance variables by using an Annotation. If an annotation for a class has a key of InstanceVariableType, then the value is the instance variable type. The value for an InstanceVariableType annotation must be a string that when evaluated is a literal array. The first item in the literal array is the variable name and the last item is the full .NET type name (e.g., System.Object).

.NET Classes and Methods

You can reference .NET classes directly in your code by using the full name. For example, to reference the .NET object class, you need to use System.Object. If the class name isn't overridden by a Smalltalk class, you can also refer to the .NET class by its short name. For example, System.Windows.Forms.Button can be referenced as Button.

To reference the .NET variables, properties, or methods, you send messages. The first keyword of the Smalltalk message must be the name of the item you are referencing. If you are wanting to call the constructor, then the first keyword should be #new if there aren't any arguments or #new: if the constructor takes arguments. The other keyword parts for messages or constructors can be anything. For example, to evaluate the System.Math::Round(double, int32) static method, you can evaluate "Math round: 1.234 with: 2".

.NET/#Smalltalk Object Conversions

When you reference external methods, properties, or variables, objects must be converted from a #Smalltalk object to a .NET object and vice versa. For example, an integer in Smalltalk could be a SmallInteger or a LargeInteger object, but in .NET they might be an int32, uint32, int64, etc. The .NET compiler automatically performs such conversions.

Most conversions are based on the typed instance variables. For example, if you have a class that defines a typed instance variable for System.Double (e.g., FloatD), then when you pass that object to .NET it will load the System.Double part from the FloatD. Also, when you convert from a System.Double back into a #Smalltalk object, it will create a FloatD #Smalltalk object to represent the System.Double object. There are some conversions that aren't based on the typed instance variables. In particular, integers and booleans are handled specially by the compiler. If you need to change how these are converted, then you must modify the compiler.

When converting from a .NET object back to a #Smalltalk object, it may be possible that the object being returned doesn't have a #Smalltalk class that defines a typed instance variable for that class. In these cases, the object will be wrapped in a #Smalltalk ObjectWrapper. This class uses #doesNotUnderstand: error handling to forward messages to the original .NET object.

Occasionally, they might be a .NET interface that contains an overloaded function with several types of integers (e.g., int32, uint32, int64, etc.). Since the #Smalltalk compiler just picks one, it might not pick the correct overloaded function. In these cases you can first convert your #Smalltalk integer to another #Smalltalk class. For example, if you want to call the function that takes an uint32, you can convert your integer to a UInt32 first (e.g., "Some.Net.Class SomeMethod: (UInt32 convertFrom: 1)").

Delegates

Delegates in #Smalltalk are handled by blocks. Whenever you send a message that takes a delegate, you can pass a block instead. The one restriction with delegates is that they must be known at compile time. To compensate for this limitation, the compiler has a special selector (#asDelegate:) that converts a block to a .NET delegate. The argument to the #asDelegate: message must be a .NET delegate type. For example, you can specify a callback for the clicked event of a button with:

button add_Click:
([:o :e | self doSomething] asDelegate: System.EventHandler)

Events

There isn't direct support for .NET events like there is for variables, properties, and methods. If you need to add or remove events, you will need to do so directly through the methods. For example, you can evaluate "System.Windows.Forms.Application add_ApplicationExit: [:object :args | ...]" to add an event handler for the ApplicationExit event.

Back to Sharp Smalltalk page!