Compiler

Programs compiled by the #Smalltalk compiler must be specified in the ANSI Smalltalk SIF format. For a simple example, suppose we wish to evaluate: "Transcript print: 1000 factorial". First, we must create a SIF file for this source:

Smalltalk interchangeVersion: '1.0'!
Global initializer!
"Here to add a prereq annotation"!
Annotation key: 'Prerequisite' value: 'Smalltalk.sif'!
Global initializer!
Transcript print: 1000 factorial!

The Prerequisite annotation tells the compiler to include the Smalltalk.sif file. That file contains all of the Smalltalk classes for the base class library. You will need to include that file for any program that you write. Once you have entered the source code, you need to save it to a file. In this case, we can save it as "factorial.sif". Now we are ready to compile the file. To compile the file, we can simply evaluate "sst factorial.sif" from the command line. Once the compiler finishes running, it produces a factorial.exe program that can be run from the command line (you will need copy the LargeInteger.dll to the same directory as your factorial.exe program).

Sample Programs

There are some sample programs shipped with #Smalltalk that you can compile. The Calculator.sif program is a simple command line calculator like bc. It support +, -, *, /, ^ (exponentiation), and ! (factorial) (e.g., "10000! / 9999!"). There is also the #Smalltalk compiler. It is written in #Smalltalk and is in the sst.sif file. The Interpreter.sif is a command line interpreter. You can enter expressions separated by !. The results of each expression is printed to the command line.

If you download the examples, then you can also compile the Benchmarks.sif and the ANSI tests derived from the Camp Smalltalk work. The Benchmarks.sif file contains Squeak's #tinyBenchmarks and Bruce Samuelson's Slopstone & Smopstone benchmarks. The ANSI tests are in the ANSITests.sif file. If you run these, it should print that all 3046 tests pass.

Glorp and Swazoo are also included in the examples. The Glorp tests are in the GlorpTests.sif file. Assuming that you have changed the data source in the GlorpDBTests.sif file, all tests should pass. The Swazoo-Tests.sif file has the tests for Swazoo -- all tests should pass. You can read the readme.txt file for information on setting up a Swazoo server.

Compile Options

You can specify some compile time options when compiling your program. You can get a list of these options by just running the compiler without any input files.

Program type: You can specify the program type by using one of --DLL, --CONSOLE, or --WINDOWS. Both console and windows options produce .exe files that can be run link normal programs. The main difference is that console applications are run by the DOS command line. Control is returned back to the command line after the program finishes. However, if you use the --WINDOWS option then control is returned when the program starts. Essentially, the --WINDOWS option is for GUI applications.

Libraries: You can use any .NET class in #Smalltalk. However, for classes that aren't in the standard mscorlib.dll library, you will need to tell the compiler where to locate the class. You can do this by using the --LIBRARY= option. For example if you want to link to the Windows forms library, you can use --LIBRARY=System.Windows.Forms. Once you have linked to that library, you could compile code that uses the message box (e.g., "MessageBox show: 'this is a test'").

Output: Normally, when you compile a program it makes the program have the same name as the last input file. For example, if you run "sst test.sif", it saves a "test.exe" program. However, sometimes you want to save to a different filename. For example, if you are modifying the #Smalltalk compiler, you will want to save the compiler to a different name. You will want to test out the new compiler before replacing the old one. You can specify the name of the output by using the --OUTPUT= switch. For example, if you run "sst test.sif --OUTPUT=foo", then it will create a foo.exe program instead of a test.exe program.

Primitives

Most Smalltalk's specify primitives by special "<>" tags at the beginning of the method. #Smalltalk does not do this. Instead it uses a special message sent to "Compiler". This allows you to insert a "primitive" in the middle of a method. The format of this message is "Compiler primitive: [:codeGenerator ... | code to run] ({evaluate: code} | {argument: code} | {usesArgument: code})*". When this is compiled, the code specified by the primitive: block is evaluated. Arguments specified by the evaluate: argument are pushed on the stack before the code in the primitive block is run. The arguments given by the argument: and usesArgument: are passed to the primitive: block as a parse tree.

Special Messages

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.

Contact Us

  • 1-217-344-4847
  • info@teamsthatinnovate.com

About Us

Teams that Innovate offers high-level, customized consulting and mentoring services to industry leading companies. Our goal is to hasten the improvement of your core development teams, empowering them to innovate, grow, and develop clean, functional code.

Copyright © 2013 - Present, All Rights Reserved.