- The Basics
a. A very basic example
b. Something slightly more complex
ii. Creating data structures using types
- Something more powerful
a. Example 1 – Drilldown Application
b. Example 2 – ___ Application
- Source Code
a. Example 1
User-defined types are a significant advantage for any serious programmer, and can even be useful to those less experienced. At their simplest, these may consist of little more than a combination of value types that can be used together as a single unit.
First Some Basic Information
Type definitions must be located outside of any function, but do not need to precede them. In fact, custom type definitions can be located in an included file and not be in the main source code at all.
There are three (technically four) keywords that can be used when creating a custom type:
resolve. By default, properties will be added as references to a specified type (or any type when using
type(*), any value using
type(=), or any matching tagged type when using
To embed a property the
embed keyword can be added to the end of a statement. To switch the default from referencing to embedding properties place the embed keyboard as its own line in the type definition. To switch back simply do the same with the
reference format. These switches only apply within that type definition
A quick note about embedding objects: There are some objects which cannot be embedded, such as
fsfileoutputstream or any of the
ppcstype1 objects. However, references to any object type can be part of type definitions.
The last proper keywords is a special one. Normally when properties aren’t embedded they are not examined when trying to resolve the name of a property or method, but if the
resolve keyword is added to the end of the property definition, then at runtime the property itself will be included when searching for a property or method that is not listed at the first level of the type definition. This will become clearer later.
A Basic Example
A very basic example of a custom type is a structure containing locale information. This type of structure would need to be passed into any function that is going to format a number, date, time or which converts these from a string representation to a more useful one. Such a structure might look like this:
type tLocaleInfo string sDecimalSep embed string sThousandsSep embed string sListSep embed
This very basic example shows the convenience of custom types, this is generally far more convenient than passing and keeping track of each of these individually.
Something slightly more complex
This example is a gradual introduction into custom types, building into something more complex with functions
type mytype string m1 end type
In this basic code
m1 contains a reference to a string object.
If we add the
embed keyword to the end of this line, however, it will now contain an actual string object, and not just refer to one
type mytype1 string m1 embed end type
To use these two types the following little pieces of code will come in handy:
function main() mytype m string s m =@ mytype.new() s = "hello" m.m1 =@ s s = "foo" end function m.m1
This function will return foo since
m.m1 contains a reference to
function main() mytype1 m m =@ mytype1.new() m.m1 = "hello" end function m.m1
This will return hello, since
m.m1 is an embedded string.
The following couple examples will all still use
mytype1 but will all return errors
function main() mytype m m =@ mytype.new() m.m1 = "hello" end function m.m1
This will result in an error since
hello is not an object, it is a value and this type can only hold a reference to an object.
type mytype2 mytype mm1 embed mytype mm2 end type function main() mytype2 m string s m =@ mytype2.new() s = "hello" m.mm1 =@ mytype.new() m.mm1.m1 =@ s end function m.mm1.m1
This example will return Error 52 Not Embeddable since we have not defined
mytype as embeddable. To fix this we have to rewrite mytype
type mytype embed string m1 end type
This makes the type itself embeddable.
type mytype embed string m1 end type type mytype2 mytype mm1 embed mytype mm2 end type function main() mytype2 m string s m =@ mytype2.new() s = "hello" m.mm1.m1 =@ s end function m.mm1.m1
Creating data structures using Custom Types
Custom types can also create more complex data structures such as lists and trees in memory. The following is an example of a singly-linked list which should return hello.
type mytype string m1 mytype next end type function main() string s mytype m,mfirst s = "hello" m =@ mytype.new() mfirst =@ m m.next =@ mytype.new() m =@ m.next m.next =@ mytype.new() m =@ m.next m.next =@ mytype.new() m =@ m.next m.next =@ mytype.new() m =@ m.next m.m1 =@ s m =@ mfirst mfirst =@ .nul end function m.next.next.next.next.m1
Something more powerful
The previous examples have all consisted of only values and references to values but have not included any custom methods, to make more powerful tools we will now add some methods to these functions.
Any user-defined type can also have a user-defined
new() method, this allows the programmer to initialise the newly created object when it is created.
For those newer to programming, it might be useful to first explain what a method is and how it differentiates subtly from a function. In practical terms there is no difference between the two in SIMPOL, and if you wish to call them functions instead of methods that’s fine. However, the term method is more applicable than function here as methods are defined as part of a (normally internal) class, which is the case here
Now to defining your very own custom method, the syntax for this is very easy, simply add the keyword
function followed by the method name to your type declaration (as below).
As you may have spotted from the above examples every type comes with an inbuilt
new() method, this does not need to be listed inside the type definition unless you wish to reinitialise the object after its initial call.
Note: custom methods must be defined in the same module (.sma file) where the type itself is defined and must follow the type definition in the code file.
Creating the method is much like creating any other function, except before the function name you have to add the type and a dot operator
function tCustInfo.copy(tCustInfo me)
As you can see from above the first argument to any method must be the type itself.
Note: in the case of the
new() method it must return the object of the type that was passed in, otherwise the assignment to the variable will fail.
If that didn’t make sense maybe the following code can clear things up for you:
type tCustInfo export string sCustID embed string sFirstname embed string sLastname embed datetime dtCreated embed string sCreatedBy embed function copy end type function tCustInfo.new(tCustInfo me, string sCreatedBy) me.dtCreated.setnow() me.sCreatedBy = sCreatedBy end function me function tCustInfo.copy(tCustInfo me) tCustInfo copy copy =@ tCustInfo.new(me.sCreatedBy) copy.sCustID = me.sCustID copy.sFirstname = me.sFirstname copy.sLastname = me.sLastname copy.dtCreated = me.dtCreated end function copy
This example shows a user-defined type which implements a
new and a
copy method. The
copy() method is implemented so that it produces an exact copy rather than a copy with a potentially new creator ID and new creation datetime.
Export and Readonly
You may have noticed a keyword I haven’t mentioned yet:
export this keyword ensures that the type is visible outside the module, this is useful as the file can then be added to a project as a pre-compiled module (Library) that can be added to a project either at compilation or at runtime.
Note: Functions do not require the export keyword as they are made available within the type itself.
On the subject of keywords, there is one more I haven’t mentioned yet:
readonly this is because it doesn’t occur in the normal use of SIMPOL and is used mostly in the advanced stages of programming while compiling large custom types similar to SIMPOL’s included libraries
The following are examples from other tutorials of varying difficulty to help you get to further familiarise yourself with custom types in SIMPOL
This is an example required for our drilldown library guide
Before we can get into the meat of this tutorial we must first create a custom type that will hold a reference to our table.
type drillapp (application)
application __app resolve
This type only uses the
resolve keywords. Reference is the default for any new variable and simply means our custom type will now refer to a value. Resolve is a more specialised use case, it allows us to call methods from other types in our own. In this case, we can now call methods from the
application type by referencing
Because we are resolving a variable we should declare a
function drillapp.new(drillapp me)
As this a more advanced tutorial I will leave you to declare all the relevant variables as they come up (or just look at the full source code at the bottom of the page)
me.__app =@ application.new(appiconfile="", iconimagetype="", inifilename="", apptitle =sAPPTITLE)
me.__app.__ =@ me
This code declares a new application and refers it to our
__app variable, it then assigns
drillapp to be a wrapper object for this application by referencing it to the
__ variable. This is done so we can reach the container object from the contained object, as well as giving our new type the type tag of
(application) so it can be passed to functions expecting such a type
The next thing we will be doing is declaring our
onexitrequest function for the application
me.onexitrequest.function =@ exit
The first thing to note is that our drillapp has no
onexitrequest declared, this is only possible since we have now declared and resolved our
__app variable. The second thing to note is that
exit is not a standard function so we will need to declare it (this will not be done here)
After we have declared our application we should create a window for it to stay in, an
appwindow to be precise. In this example, this will not be very difficult as we are not implementing a menu, toolbar, or status bar so nothing fancy will need to be done
appw =@ appwindow.new(me,visible=.false,mb=.nul,tb=.nul,sb=.nul,border="simple",maxbutton=.false)
We need to start the window as invisible as we draw the form on before making it visible, we will also be using a simple border instead of the standard sizeable one as we don’t want the user to adjust the size, to accompany this we have also disabled the maximise button
Getting the database
This is the most basic way of getting a database file since it is only one database, and one table opened using SBME1. Nevertheless, it is important to know how to open it properly
The first thing we need to do is get the location of our CUSTOMER database and then open that database
file = getpublicdatadir() + "\SIMPOL Business\customer.sbm"
src =@ me.opendatasource("sbme1", file, appw, error=e)
The database we will be using can be found in your public documents directory, we will then open this file
Opening the table
The next thing we need to do is open the table itself, and reference it back to our type
t =@ appw.opendatatable(src,"CUSTOMER",error=e)
me.customer =@ t
ok = .true
There is a mechanism in place at the very end of this method that will set the drillapp to
.nul if something has gone wrong in the initialisation. This is done by declaring a boolean as
.false at the top and only changing this boolean to
.true after the table has been opened. After exiting all the error checking if statements the following code will check if everything happened as expected
if not ok
me =@ .nul
This type has no additional functions and is very basic, in more advanced cases you would add additional methods the example below is an example of a more complicated custom type
Example 1 Source Code
type drillapp (application) reference application __app resolve type(db1table) customer end type function drillapp.new(drillapp me) appwindow appw datasourceinfo src type(db1table) t integer e boolean ok string file ok = .false e = 0 me.__app =@ application.new(appiconfile="", iconimagetype="", inifilename="", \
apptitle =sAPPTITLE) me.__app.__ =@ me me.onexitrequest.function =@ exit me.running = .true appw =@ appwindow.new(me,visible=.false,mb=.nul,tb=.nul,sb=.nul,border="simple", \
maxbutton=.false) if appw =@= .nul wxmessagedialog(appw.w, "Error creating window", sAPPMSGTITLE,icon="error") else file = getpublicdatadir() + "\SIMPOL Business\customer.sbm" src =@ me.opendatasource("sbme1", file, appw, error=e) if src =@= .nul wxmessagedialog(appw.w,"Error opening customer.sbm file",sAPPMSGTITLE,icon="error") else t =@ appw.opendatatable(src,"CUSTOMER",error=e) if t =@= .nul wxmessagedialog(appw.w,"Error opening the 'Customer' table", \
sAPPMSGTITLE,icon="error") else me.customer =@ t ok =.true end if end if end if if not ok me =@ .nul end if end function me