GMC (or Grendel MUD C if you will) is a language similar to C, devised for the sole purpose of powering the scripted parts of the Grendel MUD Server.
It has support for variables, control structures, (nested) functions and external variables/traps.
The system consists of a compiler, an assembler and a virtual machine setup which can be called from an application, in this case Grendel.
The GMC language is in many respects quite like C, with some simplifications and exceptions.
The most important supported language constructs:
global and local variables
loops (only for currently)
basic flow control (if statements)
valid integer and boolean expressions
The GMC compiler works in four distinct passes.
In the first pass, the source files are scanned and parsed, and basic syntax checking is performed.
The second pass performs type checking and coercion.
In the third pass an optimizer performs some basic optimizations: dead-code elimination, algebraic simplication (constant elimination) and some flow-of- control optimizations.
The fourth and final pass is the code generator, where assembly code for the stack machine is written to a file.
The assembler simply converts the "human readable" assembly code to a binary format (this includes translating the labels for jumps/calls).
Additionally, the assembler performs some simple optimizations (in a section commonly known as the 'peephole optimizer'), to reduce the number of instructions.
The VM of the GMC language is a fully functional stack machine processor with some 40+ opcodes in its instruction set.
Internally the machine consists of a stack utilizing the Delphi 'variant' type.
Apart from the stack there are a number of registers - the stack pointer, the base pointer, and the program counter (or instruction pointer). Only the first two can be accessed directly from the assembler.
Lastly there is a data segment present. Its size is defined through the assembler directive '$DATA'.
The VM has support for a number of mechanisms to help a program 'get out' of the environment and have some interaction with the calling application.
These mechanisms are:
Signals and sleep
The system trap (in the form of do("some expression")) can be set to a userspecified function by the calling application. The trap can do with the string expression as it sees fit.
Signals are used to "suspend" the execution of the program until some condition has been met (e.g. a signal has been raised) in another part of the server (wether that be native or scripted code). The sleep function suspends the program until a certain tickcount has been reached.
External variables are a construct to associate external data with names or expressions in the GMC language.
External methods are methods within a class, that are registered before a script is executed. In a script they are represented by a function without a body ('float cos(float x);' for example). These 'callbacks' are somewhat dangerous, as there is no mechanism to check the validity of the parameters specified in the declaration, nor the values provided at the time of calling.
GMC only allows basic (primitive) types - thus compound and structured types or arrays are not included in the language.
The following basic types are supported:
Mapped to the pascal type integer
Alias for int
Mapped to the pascal type single
Mapped to the pascal type LongString
Should not be used for basic identifiers
A special type; it has no strict semantic meaning in itself, but will evaluate to a callback to the shell surrounding (or calling, if you will) the VM. This callback will take care of the necessary checks (including or excluding typing) and return a value associated with the external.
Executing this snippet will have the external callback trying to associate the string value "Grimlord" with some enviroment. If this association succeeds, the resulting value will be placed in 'ch'.
Again, this will have the callback trying to associate the member variable 'alignment' with the (already associated) variable 'ch'. Care must be taken that this member variable does actually exist, or hell will fall down and chaos will ensue.
An 'entry point' is a position in your script code that has a common syntax or structure, and can be easily identified by the calling process. These entry points can be viewed as seperate blocks of code that are called when a particular event is triggered.
Below you will find some common entry points that are used throughout the server code.
void onTick(external ch)
Called every second
void onAct(external ch, external target, string arg)
Called when ch receives some action arg from target
void onFight(external ch, external target)
Called every fight tick
void onDeath(external ch, external target)
Called when ch dies at the hands of target
void onReset(external ch)
Called when ch resets (spawns)
bool onBlock(external ch, external target, string dir)
Called when target tries to leaves the room where ch is (in direction dir). Returns true when blocking
void onGive(external ch, external target, external obj)
Called when target gives obj to ch
void onEmoteTarget(external victim, external actor, string arg)
Called when victim is target of an emote executed by actor (e.g. slap victim). The name of the emote (Name:-field from system\socials.dat, e.g. 'SLAP') will be in arg.
When an entry point onEmoteTarget exists, the default emote handling (as defined in checkSocial() in mudsystem.pas) will not be used.
void onGreet(external ch, external target)
Called when target enters the room where ch is