- The Basics
a. A very basic example
b. Something slightly more complex
i. Errors
ii. Creating data structures using types - Something more powerful
- Examples
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: embed
, reference
, and 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 type(<tagname>)
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 fsfileinputstream
, 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
end type
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 s
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.
Errors
The following couple examples will all still use mytype
and 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.
Custom Methods
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).
function copy
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
Examples
The following are examples from other tutorials of varying difficulty to help you get to further familiarise yourself with custom types in SIMPOL
Example 1
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)
reference
application __app resolve
type(db1table) customer
end type
This type only uses the reference
and 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 drillapp
Initialising drillapp
Because we are resolving a variable we should declare a new()
method:
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)
Appwindow
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
end if
Further notes
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 2
Source Code
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