![]() |
Menu |
![]() |
||
| Introduction Documentation Download |
|
Development Bugs News |
||
| abstract |
Entry |
Inline |
override |
rename |
true |
| asm |
Enum |
inherited |
out |
return |
try |
| auto |
Exp |
is | pascal |
self |
var |
| break |
Extern |
Last | Prev |
sizeof |
virtua |
| chapter |
Except |
Lib | Process |
shl |
write |
| cdecl |
false |
loop | Prop |
shr |
union |
| const |
fastcall |
message |
Private |
Static | hook |
| Constructor |
finally |
Next | Pos |
stdcall | Os |
| continue |
First |
Nil |
Public |
Store | Platform |
| default |
In |
nrrecords |
raise |
Struct | |
| Destructor |
Index |
of |
read |
Space | |
| Entity |
Include |
operator |
register |
Type |
Gene uses code units as the input for the compiler. Code units are text files with the extention 'gen', containing source code. A project is the main code unit that is being compiled. For all the processes and libs on the project unit an output will be generated.
Code units are linked together through the use of the include
statement
Include = "include" UnitName {"," UnitName} ";"
UnitName = string
Examples: Loading sequence explenation
include StdIo, files;
include baseStore;
Code unit a:
include b;
//do1
include b;
//do2
Code unit b:
//do3
include a;
//do4
will result in the following sequence:
do3
do1
do2
do4
public
space x =
var a: int
;
private
space y =
var a: bool
;
global
space z =
var a: pointer
;
//line comment
include a; //another line comment
/* a block comment that starts here
and ends here */
pragma
[ "interpretedict" ["on" | "off" | "excl"]
| "pack" [ ["push" | ] [1|4|8|16] | "pop" ]
| ["freelib" | "loadlib" | "procaddress" | "alloc" | "free"] designator
| "ProcessInstance" designator
| "UniCode" ["on" | "off"]
] ";'The pragma keyword allows you to configure the compiler settings from wihin the code. The compiler must recognise the instruction, otherwise an error message is generated.
| Name |
signed |
Nr of bits | floating point |
|---|---|---|---|
| char |
true |
8 |
no |
| byte |
false |
8 |
no |
| short | true |
16 |
no |
| word | false |
16 |
no |
| int | true |
32 |
no |
| dword | false |
32 |
no |
| long | true |
64 |
no |
| qword | false |
64 |
no |
| float | true |
32 |
yes |
| double | true |
64 |
yes |
| extended | true |
80 |
yes |
| bool | false |
32 |
no |
| pointer | false |
32 |
no |
| IdType | false |
32 |
no |
the 'IdType' is able to store run time type information of other objects.
a: int
b: IdType
b = a //b contains the type of a
b = bool //b contains bool type
[<-b == int-> //if b cotnains type int, do...
a = 1
]
b = true //illegal code
"enum" Identifier "=" [Identifier ["=" integer] {"," Identifier ["=" integer]}] {ExpDecleration| MessageSend} ";"
The enum keyword allows you to define a set of constants of type int. Internally, all enumerators are presented as integers of 32 bits. Any int value can be assigned to the enumerator, no checking is performed. Enumerators can also be sent to message receivers.
example:enum ObjectType = Point = 1, square, triangle
exp reset = self^ = square ;
;
"struct" Identifier ["(" [ Identifier {, Identifier} ] ")"] "="
{ Variables
|Properties
|Expression declerations
|overloaded operators
|Constructors
|Destructors
|MessageSendDefs
}
";"
A structure is used to group several variables into 1 record that can be used as a type. Besides defining which variables go into the structure, other information on what can be done with the structure are also defined such as:
Structures don't store any run time type information. This means that there can not static static variables or virtual expressions. It is also not possible to check the type at run time.
"var" {VarItems}
VarItems = Identifier ":" TypeDecleration ["=" ConstValue]
The var block specifies all the fields in a structured type. Variables are always accessible by the outside world. When generating the memory locations, the order of decleration is observed. For all the decleration possiblities, see Type declerations
example:var x: int
y: int = 10
"prop" {identifier ":" TypeDecl
["read" [ExpDecleration | identifier |"var" MemLoc]]
["write" [ExpDecleration | identifier |"var" MemLoc]]
}
struct x =
var a: int
prop b: int read var c: int
var d: int
;
struct x =
var a: int
c: int
d: int
;
struct x =
prop a: int read exp GetVal: int write var b: int
;
exp x.GetVal =
return(b)
;
struct x =
var b: int
prop a: int write b
;
"exp" { ExpDefinition}
ExpDefinition = Identifier ["("[ MemLoc {","MemLoc} ")" ] [":" TypeDecleration]
["in" identifier[":" identifier] {", " identifier[":" identifier]} ]
["out" identifier[":" identifier] {", " identifier[":"identifier]} ]
["inline"] ["static"] ["extern" [identifier] ] ["abstract" | "override"|
"virtual"] ["default"]
[ExpImplementation]
ExpImplemention = "=" {Statement} ";"
An expression is the equivalent of a function. It is called expression cause unlike functions, expressions can contain a form of regular expressions (inteface statements). For more on this, see Message statements
Just as functions in most other languages, an expression can have parameters and/or a result type. For parameters by value of complex types, a local copy will be made using the assign operator if it is defined, otherwise by simply making a copy of the data.
The special keywords "in" and "out" allow you to specify to/from which message handlers the expression can send and or receive data. This needs to be specified if you want to use message statements in the code. A message handler can be specified as a normal var decleration (identifier: typedecleration) or by specifying the name of the global variable (identifier).
The keyword 'Inline' specifies that whenever a call is made to the expression, the body should be copied inside the caller instead of making a call.
The static keyword defines that the expression can be called from the type. Static expressions can't access field data.
Extern expressions are only allowed in libraries and processes. Extern expressions can be called from outside the library, they are exported. Possibly, an extra name can be specified in case the name of the function in the library is different than that of expression. This allows you to use different names in the object than the actual names found in the library and it also allows you to link to case sensitive libraries such as the ones produced by C compilers.
Abstract, override and virtual are only allowed in complex types (spaces and stores). Abstract expressions don't have code associated with them but need to be implemented by a descending object. If you want to allow an exprssion to be reimplemented by a descendent, you use the virtual keyword. Expressions that are reimplemented need to use the override keyword. Virtual and abstract expressions will make certain that the correct implementation of the expression is used for the caller. If you reimplement an expression without initially declaring it abstract or virtual in the base type, you will always use the expression that is located in the type of the variable, not in the data's real type.
The default keyword can only be used with constructors and destructors. For more info on this, see constructors or destructors.
After an expression decleration, you can always give the implementation of it.
OperatorOverlaoder = {"'"Operator"'" ExpDefinition}
Operator =[ "*" | "/" | "%" |"&"
|"+" | "-" | "|" | "~"
|"=="| "<" |"<=" | ">" | ">=" | "<>"
|"=" | "+="|"-=" | "*="| "/=" |"%=" | "<<" | ">>" | "<<-" | "->>" ]
//create an operator overloader so we can assign a char to the object
operator '=' DoAssign(aVal: char)
//a multiply operator for a vector type that is reversed so we can
//write aint * avector
operator '*' DoMul(aVal: int, aVector: sVector) static
Constructor = "constructor" {ExpDefinition}
constructor create = ; default
Detructor = "destructor" {ExpDefinition}
destructor destroy = ; default
"message" { identifier ["read"ExpDefinition] ["write" ExpDefinition] ["is" ExpDefinition]}
For more info on message principles, see messages.
With message receive definitions you can define which objects can be sent to the defining object. You need to specify the object type that we can send to, the read, write and verify function.
The read function will get a value from the defining object. The write function will write the value. The verify function will check if the requested type is available
For this to work, each function needs to have a set number of parameters and a specific return value.
space sContainer =
entity pointer //stores refs to anything
store stLinkedList //store the data as a list
var CurPos: int = 0 //stores at which position we are currently
Next DoNext = CurPos += 1;
;
space sElement =
var Val1: string
message string read ReadFromContainer(aContainer: sContainer&): sElement static
write WriteToContainer(aContainer: sContainer&) inline
is IssElement(aContainer: sContainer&): bool static inline
exp sElement.ReadFromContainer(aContainer: sContainer)
=
return(sElement^(aContainer@CurPos.entity)^)
//note: don't advance, this is done by the GetNext of the container object
;
exp sElement.WriteToContainer(aContainer: sContainer)
=
//add ourselves to the list
aContainer << pointer(self)
;
exp sElement.IsElement(aContainer: sContainer): bool
=
//check if the object at the current position is of our type
[<-aContainer@CurPos is sElement-> return(true)
<--> return(false)
]
;
"union" identifier "=" { Var | ExpDeclerations } ";"
To select a variable within the union, use the "." selector.
example:union uIntAndFloat =
anInt: int
aFloat: float
;
aVal: uIntAndFloat
aVal.anInt = 5
aVal.aFloat = 5.5 //value of anInt is
overwritten
Complex types are types that contain run time type information. There are 2 variations: spaces and stores. Since complex types have run time information, they can contain static fields and virtual expressions. Complex types can also inherit from one or more parents (multiple inheritence). All parents must be of the same type as the current type. Or in other words, a space can only inherit from other spaces and a store only from other stores.
You can also check if a complex object is of a specific type or if the type is in the inheritence tree of the object by using the '=' and 'is' operators.
"space" Identifier ["(" [ Identifier {, Identifier} ] ")"] "="
{SimpleVar
|PropDef
|ExpDeclerations
|OperatorDefs
|constructorDefs
|DestructorDefs
|MessageSendDefs
|MessageReceiveDefs
|"entity" TypeDecleration
|"storage" Identifier
|"Next" ExpDefinition
|"space" Designator
}";"
A space is a complex type that expands the struct type (a struct type with run time type information). A space can also receive objects that are send to it through the messageing system. Furthermore, a space can operate as a container for other object of a specific type.
The following capabilities can be defined in a space:
To define a space as a container, you use the "entity" and "storage" sections. Under "entity" you specify a type decleration of the object that you want the store. The "storage" keyword is used to let the system know how it should manage this data. This has to be the name of a store. A space doesn't have to be a container. If the entity and storage keyword are not suppplied, it is impossible to store data in the space.
If the first parent space defines the sections, descendants will inherit these settings. They can not be overwritten. All secundary parents can retain their container definitions. This means that a space can actually have multiple containers, one of it's own and one for ech of the secundary parents which can only be reached by casting to the parent type.
Normally, an entity always keeps a reference to the space that it
owns, but if the entity is a structured type that already contains a
reference to it's owner, you don't need to store this information
2 times, therefor, you can use the keyword 'space' to indicate which
field of the entity has the ref to the owner.
There are special operators provided to add and remove data from a
space. There is also a general way to loop through the
data.
When data is added or removed to/from a space, this is done through
the store that it defined. Sometimes you need to call some extra
code, specific for this space during these operations. This can
be done be implementing an operator overloader (for the add and remove
operators) with as parameter, a reference to the space entity. In
the case of an add, this expression will be called after the actual
operation, for the remove, it will be called before the data is removed
from the space.
//a simple string type
space sSimpleString =
entity char
storage stArray //this is a store defined somewhere else and represents an array
;
//an example using space
space sOwner; //forward declare
struct sChild =
var Owner: sOwner^ //a ref to the owning object
;
space sOwner =
entity sChild //the object type we store
store stLinkedList //how we store it
space Owner //where can we find the owner in the object type we store
;
//an example with an operator overloader for the add and remove in the space
space sOwner =
entity sChild
store stLinkedlist
operator '<<' AddElement(aValue: sOwner.entity) = aValue.Owner = self;
operator '>>' RemoveElement(aValue: sOnwer.entity) = aValue.Owner = nil;
;
"type" {identifier ["read" ExpDefinition] ["write" ExpDefinition] ["is" ExpDefinition]}
For more info on messaging principles, see Messages.
With message receiving definitions you can define which objects the can be sent to the defining object. You need to specify the object type that we can handle, the read, write and verify function.
The read function will return a value of the type this message is suppose to handle. The write function will write the value from the defining object to the message object. The verify function will check if the message handler can read the value(s) as the specfied type.
For this to work, each function needs to have a set number of parameters and a specific return value.
space sIntListString(string) =
var CurStart: int = 0
CurEnd: int = -1 //so that we have a proper first init
type int read ReadFromString: int
write WriteToString(aVal: int^) inline
is IsInt: bool inline
Next DoNext
constructor create default = DoNext;
;
exp sIntListString.ReadFromString: int
=
//get the substring out into a temp string, convert that into an integer
iTemp: string
iTemp = self.SubString(CurStart, CurEnd)
return(Convert.StrToInt(iTemp))
;
exp sIntListString.WriteToString(aVal: int^)
=
//convert the int to a string, add it to us
iTemp: string
iTemp = convert.IntToStr(aVal^)
self += iTemp + " "
;
exp sIntListString.IsElement(aString: string): bool
=
//There is an integer waiting if the start is different from the end
return((CurEnd - CurStart)> 0)
;
exp sIntListString.DoNext
=
//find next space, check if we found an alpha numeric value, if so,
//set start and end to end, otherwise, it was a number so leave start
iNeedEqual: bool
[<-(CurEnd + 1) < Self.NrRecords->
iNeedEqual = false
iChar: char
CurEnd += 1
iChar = Self@CurEnd
{<-CurEnd >= Self.NrRecords->
>-iChar == " "-> break
<-->
[<-iChar < "0"|| iChar > "9" -> iNeedEqual = true]
CurEnd += 1
iChar = Self.@CurEnd
}
<--> iNeedEqual = true
]
[<-iNeedEqual-> CurStart = CurEnd ]
;
"store" Identifier ["(" [Identifier {, Identifier} ] ")"] "="
{ SimpleVar
| PropDef
| ExpDeclerations
| OperatorDefs
| constructorDefs
| DestructorDefs
| InterfaceSendDefs
| "Loop" ExpDefinition
| "space" {MemLoc}
| "entity" {MemLoc}
| "first" ["read" ExpDefinition] ["write" ExpDefinition]
| "last" ["read" ExpDefinition] ["write" ExpDefinition]
| "next" ["read" ExpDefinition] ["write" ExpDefinition]
| "prev" ["read" ExpDefinition] ["write" ExpDefinition]
| "NrRecords" ["read" ExpDefinition] ["write" ExpDefinition]
| "pos" ["read" ExpDefinition] ["@" ExpDefinition] ["write" ExpDefinition]
}
";"
Type decleration = [ [Identifier | designator "." "entity"] [ [ "$" | "&" {"^"}]
| "." Constructor
| "@" Integer
]
| Struct
| Union
| Space
| Store
| ExpDecleration
]
Memory locations need to have a type
decleration. This can be://decleration of a variable of type int, initialized to 0
a: int = 0
//array of 5 elements of type int
a: int@5
//pointer to type int (or variable length array), initialized to nil
a: int^ = nil
//pointer to type int that will automatically be initialized to nil and
//will be destroyed once it gets out of scope
a: int$
//nested definition of a space (structured object)
a: space x =
entity int
storage stLinkedList
;
//definition of a struct object and variable that initializes the fields
struct x =
a: int
b: int = 0
constructor create = ; default
destructor destroy = ; default
;
a: x.Create(\ a = 2, b = 3)
"rename" identifier "=" [TypeDecleration | ExpDecleration ] ";"examples:
rename rWidth = int;
rename rIntAsString = string;
rename rStringInitToJan = String.Create('Jan');
rename rSimpleExp = exp (aValue: int) virtual;
aWidth: rWidth
aInt: int = 1
aWidth = aInt //will work
aWidth = rWidth(aInt) //using a cast will also work but is not needed
Constant = "const" {Identifier "=" ConstValue }";"
ConstValue = ["nil" | integer | extended | String | Bool | Char]
Variable = "var" { Identifier ":" TypeDecleration } ";"
examples: const
gInt = 1
gFloat = 1.1
gString = 'This is a test'
gBool = false
gEmpty = nil
gChar = "t"
;
var
gString: string.create('initValue')
gInt: int = 0
gFloat: float
gPerson: sPerson.Create(\ Name = 'Jan Bogaerts', Sex = Sex_Male)
;
"process" identifier ["(" Identifier ")"]
{ "OS" ["linux" | "win32" | "win32consolse"]
| ["platform" "x86"]
} "="
{ "entry" ExpDefinition
| SimpleVar
| ExpDeclerations}
}
";"
"lib" identifier ["(" Identifier ")"]
{"OS" ["linux" | "win32" | "win32consolse"]
| ["platform" "x86"]
} "="
{ "entry" ExpDefinition
| "return" ExpDefinition
| ExpDeclerations
|constructorDefs
|DestructorDefs}
}
";"
| Type |
Os |
Definition |
|---|---|---|
| Process |
Win32 |
main(CurIntance: THandle,
PrevInstance: THandle, CommandStr: char^, CmdShow: int): int |
| Win32Console |
main(Argc: int, Argv: char^^):
int |
|
| Linux |
main(Argc: int, Argv: char^^):
int |
|
| Lib |
Win32 |
main(hinstDLL: THandle,
fdwReason: dword, lpvReserved: pointer): bool (for entry and exit point) |
#[CurrentProcess.Osexample:
#<-Linux-> Kernel32.CloseHandle(fHandle)
#<-Win32-> CloseFile(fFile)
#<--> #errors << StrErrorLine.Create(f: Unit = Name, Line = CurLineNr, Col = CurColNr,
Text = 'Platform not supported')
#]
process foo Os linux =
entry main(Argc: int, Argv: char^^): int
var IntValue: int
;
lib foolib Os linux platform x86 =
exp dox
doa(aValue: int) extern
dob extern '__dob'
constructor create
destructor destroy
;
var StaticLib: fooLib
RunTimeLoaded: fooLib^
;
RunTimeLoaded.Create(\ Doa = '__doa')
An automation has 3 characteristics:
Some automations need extra parameters for them to be able to generate properly or to give extra options. This is done through chapters. Besides the default types such as bool, int, float, ... chapters can also be of special types such as identifiers, var, stringlists,... (see using automations for more info). The best way to think of and use a chapter is by considering the following: If a struct would be an automation (instead of a predefined type), it's chapters would be: var, prop, exp, operator, constructor, destructor, message.
AutomationName ["(" identifier ")" ] { "\" AutomationName ["(" identifier ")" ] } identifier [ typespecific ]
{ChapterName Value}
";"
There are 4 importent things you need to
know when using an automation:singletonSpace(Base) descendant =;
In this case, only the
chapter values will be inherited
//original definitionsuppose we want to observe this intire struct and it needs to be a singleton
struct x =
var y: int
;
//with observer
struct x =
varObserver y: int ObserveBefore true; //we only supply the ObserveBefore, we leave the ObserveAfter as its default, so false
;
//original definition
struct x =
var y: int
;
//with observer
SingletonStruct Observedstruct x =
var y: int
;
| Statement |
Input |
Ouput |
| constant |
|
write value |
| variable |
|
write value |
| calculation |
|
write value |
| name of type |
check if type can be found in
input let interface get the next value |
| Statement | Inteprete |
Execute |
|---|---|---|
| Variable decleration | only base types |
yes |
| Assignments | yes | yes |
| Copy statements | no |
yes |
| Shift statements | yes | yes |
| Message statements | yes |
yes |
| return statements | yes | yes |
| Options and loops | yes | yes |
| Break and Continue | yes | yes |
| Inherited |
no |
yes |
| Constructor call | no |
yes |
| Destructor call | no |
yes |
| Exception handling | no |
yes |
Identifier ":" TypeDecleration
Variables declared inside an expression are local to the block in wich they are defined. If the variable is a complex type (no pointer), and there is no constructor supplied, the default constructor is used, otherwise the supplied constructor will be used. When the variables gets out of scope or an error is raised, the default destructor will be called.
If the variable is an owned pointer, it will be initialized to
nil. When the variable is not nil at the time it gets out of
scope or an error is raised, it will be freed. If it was a
complex type, the default destructor will be called.
If the variable is declared by reference, it will create and destroy
the object .
If there is a default value supplied in the type decleration, this will be used. Note, when generating to C and the C compiler compiles as C++, variables declared inside an option or loop don't allow initilization. This is because the options and loops are constructed using goto's.
Condition ["=" | "+=" | "-=" | "/=" | "*=" | "%=" ] ConditionAn assignment copies the value from the right side to the left side. Depending on the assignment operator, it will:
a: int
b: int
a = b
c: char
a = c //error
a = int(c) //ok
aString: string
aString = 'test' //operator overloader will be used
aString += ' ' //operator overloader will be used, ' ' will be added at end of the string
Condition ["<<" | ">>" ] Conditionexamples:
a: int
a << 5 //will shift the bits in a 5 positions to the left
Condition ["<<" | "<<-" | "->>" | ">>" ] Condition
| Left |
Right |
<< |
<<- |
->> |
>> |
|---|---|---|---|---|---|
| space 1 |
space 2 |
add data from space2 to space1 |
add data from space2 to space1
and remove data from space 2 |
add data from space1 to space2
and remove data from space1 |
add data from space1 to space2 |
| space |
entity of space |
add new element to left, copy
data from right |
|||
| space |
entity of space constructor |
add new object to left reffed in
right, perform constructor |
|||
| space |
var |
add new element to left, copy
data from right |
|||
| space |
constructor |
add new element to left, store
ref in right perform constructor |
|||
| space |
nil |
remove the data from the space |
|||
| entity of space |
space |
add new element to right, copy
data from left |
|||
| entity of space constructor |
space |
add new object to right reffed in left, perform constructor | |||
| var |
space |
add new element to right, copy
data from left |
|||
| constructor |
space |
add new element to right, store ref in left perform constructor | |||
| nil |
space |
remove the data from the space |
//suppose we have the following space definitions:
space sStringList =
storage stLinkedList
entity string
;
space sIntList =
storage stLinkedList
entity int
;
var iStrings1: sStringList
iStrings2: sStringList
iStrEnt: sStringList.Entity //entity of space sSTringList
iInts: sIntList
iStrVal: string
iIntVal: int
;
//add element with a constructor
iStrings1 << string.Create('an initial value')
//add element through the entity constructor, after the operation,
//iStrEnt will point to the element that was added
iStrings1 << iStrEnt.Create
//copy the values from list 1 to list 2
iStrings2 << iStrings1
//empty list 1
iStrings >> nil
//add integer to list
5 >> iIntEnt
iIntVal = 1
iIntVal >> iIntEnt
conditionexamples:
space x =
var StrVal: string
exp DoSomething
dox in aString: string //read from a string
doy(aVal: string) out screen //write to THE screen (screen is a global parameter)
doz out aString: string, screen //write to a string and a screen
;
exp x.DoSomething =
iStr: string
dox@iStr //we have to provide an interface object, cause it is defined as a variable
//a temp var will be created and the assign operator will be called
//to copy the data of the string
doy(iStr) //don't have to provide interface, the compiler knows you mean the screen
//when the call is completed, the temp var is destroyed.
;
exp x.dox =
doz //we don't have to provide an interface, the compiler will automatically use
//the interface of dox doz will put 'test' in the interface string 'test'
//we want to read test out of the interface
;
exp x.doz =
'test' //put test in the interface
;
exp x.doy(aVal: string) =
//write the value that was passed as a parameter
aVal //ask doz to write 'test' to the screen, we don't have to provide an interface,
//the compiler will use the screen
doz
;
conditionexamples:
//suppose we have a definition of:
space a =
var b: int
exp dox in string out file
;
exp a.dox =
aVal:string = 'my age is'
b = 1
aVal ' ' b ', but could also be ' b+2
;
"return" ["(" Condition ")"]
examples:return
return(false)
"[" CodeItems {CodeItems} "]"
Use an option to perform a conditional statement. The option can be compared with the if elseif and case statement of traditional languages, combined. Execution flow is controlled through the conditionblocks. Code following the first conditionblock that is evaluated to true will be executed untill the first conditionblock that isn't defined with a fall through or the end of the option. If the first statement of an option is not a conditionblock, it should be a condition (variable, or calculation), in which case the option is considered to be a case statement. In this situation, the next statement should be a conditionblock and all condition blocks in the option should return the same type as the first statement. This means that case statements in Gene are not limited to the evaluation of integer values.
examples://simple if else if structure
[<-a == 1-> b = 2
<-c == 2-> d = 3
]
//case statement
[a
<-1-> dox
<-2-> doy
]
//another case statement
[aString
<-'test'-> dox
<-'tester'-> doy
]
//an if statement with a fall through
[<-a == 1-> dox //if a == 1 then dox and doy will be executed
>-a == 2-> doy //if a == 2 only doy will be executed
]
[ ">-" | "<-" ] Condition ["\" Condition ["\" Condition]] ["->" | "-<" ]
If the conditionblock is used in a loop, it can have multiple sections. If this is the case, the conditionblock should be the first statement in the loop and there can't be any other conditionblocks. There can't be interface calls if the conditionblock contains multiple sections. See loops for more info on the meaning of the various combinations.
If the condition inside the conditionblock is not a boolean calculation (does not contain <>, ==, >=, > , <=, <) or the left part of the boolean condition is missing, the interface system will be used. Interface calls performed by the condition block are always performed as input. If the exression has output interfaces defined, the usage of the condition blocks inside an option/loop allows the implementation of input - output expressions. Depending on the type of the condition, different actions will be performed:
After the conditionblock, before any other code is executed, the interface will be asked to advance by calling it's 'next' expression. This will not be performed if the conditionblock is closed with a '-<'. If the condition block contained an interface call through an expression, there will never be a next call. When the expression is called, the location of the interface remainded the same so that the interface call inside the expression can be handled correctly.
examples:aval: string
//if there is a string in the input, store it in aVal,
//don't advance and call 'DoSomething'
[<-aVal-<
doSomething(aVal)]
//if there is a string in the input, store it in aVal,
//goto next in interface and call 'DoSomething'
[<-aVal-> doSomething(aVal)]
//if 'test' is in the interface, goto next in interface, call 'DoSomething'
[<-'test'-> doSomething]
//if there is an int in the interface and it is bigger than 2,
//goto next in interface and call 'DoSomething'
[<- >2 -> DoSomething]
//an interface statement through an expression, the option will be executed
//if the interface currnently contains
//'x', 'y' or 'z'. Before DoSomething is called, dox will be called
[<-dox-> DoSomething]
exp dox =
a: int = 0
{<-'x'-> doa
<-'y'-> dob
<-'z'-> doc
}
dod
;
"{" CodeItems {CodeItems} "}"
Use a loop to perform a repetion of conditional statements. The loop can be compared with the while, repeat and for statement of traditional languages combined. Execution flow is controlled through the conditionblocks wich may have multiple sections. Depending on the number of sections and the location of the conditionblock, different types of execution flow can be achieved:
//a case loop: perform for as long as aVal is 'something', 'test' or 'something else'.
//If it is test, perform the code: doy doz aval= 'something'
aVal: string
aVal = 'test'
{aVal
<-'something'->
dox
aVal = ''
<-'test'-> doy
>-'something else'->
doz
aVal = 'something'
}
//a mormal loop with multiple blocks and the last block as empty: do eternal loop
{<-aVal == 5 && aVal2 > 2-> dox
<-aVal== 5-> doy
>-aVal==6->
doz
break //exits the loop
<--> doz
}
//a mormal loop with interface calls as conditionblocks: perform for as long
//as one of the values in the conditionblock can be retrieved from the interface
{<-aval-> dox
<-1-> doy
<-3-> doz
}
//a repeat loop: do for as long as aval > 0
{ dox
doy
aVal -= 1
<-aVal > 0->
}
//a space loop: loop through all the characters of a string
aStr: string
aEl: aStr.Entity
{<-aStr\ iEl-> dow(iEl.Entity)} //iEl.Entity returns the content of the space entity,
//in this case the char a for loop with increment 1 and a declare in the conditionblock:
//loop for as long as i <> 10
{<-1; i:int; 10-> dow(i)}
//a for loop with our own increment
{<-i: int =0\i < 10\i+=1->dow(i)}
"break"
"continue"
"inherited" ["(" [Condition {"," condition} ] ")"]
[designator [ "(" [calculation {"," calculation}] ["\" [assignment {"," assignment} ] ] ")" ]
| typedecl
]
//a space definition:
space x =
var a: int = 0
constructor create =;
CreateWithPar(aval: int)
destructor destroy = ;
;
space y(x) =
var b: int = 1
//constructor is inherited from x
;
exp z.dosomething =
ax: x^
//a simple constructor
ax.create
//a constructor with initial values
ax.create(\ a = 2)
ax.destroy
//a constructor with parameters
ax.CreatewithPar(1)
ax.destroy
//a constructor with parameters and initial values
ax.CreateWithPar(1\ a = 2)
//constructing an object with type that of a parent of the actual object being created
ax = y.create
ax.destroy
//and with initial values
ax = y.create(\ b = 2)
ax.destroy
;
Designatorexamples:
aStr: string^
aStr.Create //create the string
aStr.Delete //destroy the string
"raise "(" Statement ")"
examples:raise(Exception.Create('internal error'))
For more information on the various exception blocks, see:
"try"
{statement}
"except" [memloc]
statement
{"except" [memloc]
statement
}
This is the same mechanisme of exception
handling found in Delphi or
C++.
try
a: float = 5.0
a /= 0 //oeps, division by zero
except aErr: EDivByZero
screen.WriteChar('oeps, devision by 0')
except
screen.WriteChar('unexpected error')
"try"examples:
{statement}
"finally"
statement
a: string^
a.Create
try
b: float = 5.0
b /= 0 //oeps
finally
a.destroy //make certain that the string is always freed.
;
[identifier | SpaceSelector | "Self" ]
{ "." [identifier | SpaceSelector | SpaceEntitySelector]
|"(" [Condition {"," Condition}] ")" ["@" Factor]
|"@" Factor
|"^"
}
SpaceSelector = ["first" | "last | "NrRecords" | "store" ]
SpaceEntitySelector = ["Next" | "Prev" | "Pos" | "entity" ]
If the previous item in the path is the name of an expression, you can provide it with parameters and a possible interface that it needs to use. Check expression calls for more info.
If the previous item in the path is a variable of a space type, you can provide it a filter. Check Space filters for more info.
Use the '^' operator to deref pointers. This only needs to be used at the end of the path to indicate that you don't want the ref. The operator will be inserted automatically in the middle of the path as necessary.
Use the '@' operator if you want to access an element in an array (if the previous item was a pointer or array) or if you want to get to a specific entity in an interface. The factor gives the integer value of the index. In case of arrays, this is always 0 based. If it is on a space, it depends on the implementation of the store.
examples:screen //select the screen object which is a global var
aStr: string
astrEnt: string.entity
astrEnt = aStr@1 //select the char at the second pos in the string (string is 0 based)
aPChar: char^
aPChar = aStr.Store.Data //select the internal data of the string through the store
aChar: char
aChar = aPChar@1 //select the second char in the string pointer (pointer array is 0 based)
aChar = aPChar^ //select the first char through a pointer deref
"(" [Condition] ")"
The following list gives an overview of the actions taken in the different situations:
aStr: string
aStr = 'aabbaabccaa'
//cange all 'a' to d
aStr(entity == "a") = "d"
//count all the 'b'
aChar: aStr.Entity
Counter: int = 0
{<-aStr(entity == "b")-> Counter += 1}
//set all the people's house number who are male to 1, this supposes
//we have a space 'peoply' with as subrecord, address
People(Gender == "m").Address.Nr = 1
//delete all the people who are not male or female
People(Gender <> "m" && Gender <> "f") ->> nil
//print all the people address (send to interface)
People().Address
//get the first female
aTemp: People.Entity
aTemp = People(Gender == "f")
//add the house nr of the first female to the counter
counter += People(Gender == "f").Address.Nr
["+"|"-"] term
term ["+"|"-"] term
factor ["*"|"/"|"%"] factor
Condition ["="|"+="|"-="|"*="|"/="|"%="|"<<"|">>"|"<<-"|"->>"] Condition
term ["|"|"~"] term
factor "&" factor
`factor
condition ["shl"|"shr"] condition
!Factor
OrCondition "||" OrCondition
BoolCalculation "&&" BoolCalculation
designator "@" factor
"@" Factor
designator "^"
Calculation ["=="|"<"|"<="|">"|">="|"<>"] Calculation
"sizeof" "(" Designator ")"
condition "==" condition
condition "is" condition
The "==" and "is" operators are used to compare the types of an object (variable) of a complex type.
In the expressionspace x =
var a: int
constructor create = ; default
destructor destroy = ; default
;
space y(x) =
var b: int
;
a: x^
a = y.create
[<-a is x-> //this will return true
<-a is y-> //this will return true
<-a == x-> //this will return false
<-a == y-> //this will return true
]
| Operators |
Associativity |
|---|---|
| @ (array
- space index, interface) |
left to right |
| ! ` @
(get address) sizeof |
right to left |
| * / %
& |
left to right |
| + - | ~ |
left to right |
| shl shr |
left to right |
| == <
<= >= <> is |
left to right |
| && |
left to right |
| || |
right to left |
| = += -=
*= /= %= << <<- |
right to left |
| >> ->> |
left to right |
"asm" "[" {AssemblerStatement} "]"
AssemblerStatement = Opcode {operands}
Use assembler blocks to specify code snippets written in assembler.
examples:asm[
move EAX @Screen //move a ref to the global screen object in the eax register
]
Currently, the following escape sequences are supported:
| Escape sequence |
meaning |
|---|---|
| a |
audible bell |
| b |
backspace |
| f |
formfeed |
| n |
newline |
| r |
carriage return |
| t |
horizontal tab |
| v |
vertical tab |
| ' |
single quote |
| " |
double quote |
| | |
vertical line |
'This is a test |nthat needs to be put on a new line'
"|"" //a double quote char