LensCQuick Links
OverviewLensc is a fully featured programming language, allowing small programs to be written inside the mud, via OLC, without risking game crashes, or stalls. Each lensc program runs separately within the main mud engine. These programs are suspended after running a quota of instructions. They resume execution on the next mud pulse, after verifying that no data they were refering to has become invalid. This suspend-resume mechanism ensures that no builder can stall the mud with a lensc program. Each lensc program can run multiple copies at a time. The programs compile into a bytecode stream that is interpreted as the programs need to be run. As the programs are never stored in bytecode format, changes to the bytecode process don't risk damaging the game when running older programs, the worst case is that a program that used to run may require a few changes. Lensc has a runtime debugger in the mud for analyzing a program line by line. The compiler produces error messages when a program does not compile. Attempting to run a program that is not compiling simply fails. Furthermore, whenever the lensc interpreter detects an error, it will terminate execution. Sometimes, this is desirable. If the mob that called the program is slain, the program must stop. Other times, programmer error is responsible. More on this can be found in the debugging section of this document. Lensc is designed to be syntactically similar to C. It is not C however. The need to keep programs small, and protect the game integrity, prevents several C operators and constructs. Immortals will be required to get approval for their programs until they have shown competence at programming. Immortals who have shown competence will have to approve any new program before it is allowed to be run through an action. Back to topLanguage SpecificationA program is a collection of individual statements, executed in order. A language is the definition of what those statements are. Just like with a spoken language, knowing individual words does not mean the words are put together in any sensible order. "A red emu runs John at midnight." is a valid English sentence, but doesn't mean anything. Putting the sentences in the wrong order invalidates a program, just as it invalidates a sentence, "Emu midnight runs at red John a." A lensc program follows a specified format. The program definition, function and global variable declarations, and finally the main program body. Program definitionA program is defined with one line. It states the program name, and the data type of the caller. This line must come first in any program. Only char and obj types can be callers to programs. program name (datatype)or program name (datatype,datatype)The second form of prototype is for custom spell functions. The second datatype must match the expected target for the spell. Variable Declaration and Data TypestopA variable is a way to store and refer to a value that your program needs access to. Variables take memory space in the program, so only use the ones that are needed. Each variable has a specified type. Trying to assign a value of the wrong type to a variable will cause a program error and termination. The types supported are:
int i; char ch; int i = 5; int i,j; int i=5, j=6,k=7; string j = "J";Some variable types (those that refer to game elements like characters or objects) can be invalid. The variable exists, and can be used, but the data it refers to is not valid. Trying to use invalid data will cause a program to exit. There are functions that allow you to test whether a given variable refers to valid data. Singly-indexed arrays of variables can also be declared. An array is a set of variables that share a name, and have different access numbers. Arrays are declared (and accessed) with square brackets. The number of elements is specified inside the array. Note, extremely large arrays may have the side effect of abnormal program termination. int arrayone[10];Arrays cannot currently be initialized, they're set to all 0's. Variables can be declared in any block within a program. A variable declaration is a standard statement. A good programming practice is to only declare variables in the block where they need to be used. This saves space on your 'stack'. Variables declared within a block can only be accessed within that block. Variabled declared outside of any blocks are considered "global" variables. Global variables can be accessed from any function. Two global variables exist as part of every program. They are program type self; string argument;These variables are initialized when the program is run. Self is either an obj or a char depending on the definition of the program, and is initialized to the item that ran the program. Argument is the text passed to the program from the calling action. Some programs have an additional three variables: Target type target; int sn; int level;These variables are available to programs that expect to be called as part of a custom spell, and are defined as such in their inital header. FunctionstopA function is a sub-program that performs some role within a program. A function is declared with the word function, followed by an optional data type that the function will return to its caller. Finally, a list of parameters, with identifiers. Finally, a block of code defining what the function does.
function one()
{
...
}
function int two()
{
...
}
function int three(int parameter1)
{
...
}
Parameters are what the function takes as input. Within the body
of the function, a parameter is refered to by the name it is
declared with in the function's parameter list. When a function
is called, the values of the parameters are set to the expressions
resolved in the function call. Parameters are passed by value, so
any changes to the parameters within the body of the function will
not be reflected on variables outside the function body.
MainFinally, the main body of the program, and where it starts executing, is the main block of code.
main()
{
...
}
CommentsInterspersed in other code, you may always add comments. Comments are blocks of text surrounded with /* */ marks. They are ignored by the compiler, and serve only to remind the programmer why they did things. BlocksA block is a number of statements surrounded by curly brackets {}. Blocks are used to group statements together. A variable declared within a block can only be used within that block, although, variables declared outside the block can still be used within the block.
{
statement
statement
statement
...
}
StatementstopA statement is like a sentence. It is a complete thought in terms of programming. There are many different types of statements that can be used. Each represents a different sort of control in the program. A statement can be any of the following: Bold words are keywords, while italics are elements that get replaced. More detail on each statement type follows later in this document.
ExpressionstopAn expression is anything that resolves down to one value. Mathematical and logical operators fall into this category, as does calling functions (both user-defined, and system functions). ConstantsA constant is the simplest form of expression. It is a value that is unchanging during the execution of the program. Constants can be numbers (5), hexadecimal numbers (0x0a), strings ("hello"), or LensC constants (TRUE).
InputLensC supports one additional method of getting input from an external mob or object action. An action can "signal" (see mobact and objact documents for details) the program. Within the program, the keyword instring(NUMBER) is used to access this input. The instring keyword is considered a string for all intents and purposes. Once the program encounters this keyword, a timer starts, and execution stops until input is provided. The duration of the timer is measured in seconds, specified in parenthesis. If no input has been found, the program will continue, treating the input as an empty string. VariablesA variable is a value that can change as the program runs. Variables can be used in expressions either to retreive their current value, or to have new values assigned to them. Variables must have been declared before they can be used, as stated above in the variable declaration section. Arrays are a subset of variables. An array can be indexed with any standard expression.
arrayone[1] = 1;
arrayone[i] = i;
arraytwo[i*3-2] = "Hello " + i*3;
OperatorstopOperators work on one or more existing values. Operators have precedences set to them. Precedence says that in a given series of operators, which should be resolved first. This allows series of instructions to be mathematically correct. Parenthesis can always be used to override default precedences. 4 + 5 * 2 --> 14 (4 + 5) * 2 --> 18The available operators are:
Function CallstopFunctions, as described above, are blocks of code that can be called with different parameters, that serve as sub-programs. When you call a function (by using the function name and parameter list as an expression), the program's execution goes to the function's code, and runs until the function returns. The type of value that a function returns is defined in the function definition, and can be used in further expression resolution. a = 5 * generic_math_function(3,4);The parameter list is also defined by the function declaration. When calling a function, one parameter must be supplied for each parameter that the function has declared, and the parameter must be of the appropriate datatype. Using too few, or invalid typed values will cause a compilation error. Each parameter is separated by a comma. Parameters do not need to be constants, they can be any expression, including other function calls. function_one(5*4+a,function_two(20)); Complex Statement TypesIf statementstopThe if statement is used for making decisions while the program runs. An IF statement has two or three parts. The first part is the expression to evaluate. This expression is compared to zero. If the result of the expression is not zero, then the conditional statement is executed. If it is zero, then either nothing, or the else statement is executed. if (a == 5) sendt(ch,"A is 5\r\n"); else sendt(ch,"A is not 5\r\n");If statements can control blocks of text, as a block is a type of statement.
if (a==5)
{
...
}
if (a == b)
{
...
}
else
{
...
}
If statements can also be nested, as if statements are statements.
if (a == 5) sendt(ch,"5"); else if (a == 6) sendt(ch,"6"); else if (a == 7) sendt(ch,"7.");While not required, it is standard practice to indent statements controlled by an IF for readability. Switch statementstopA switch statement is another way to do selective logic. In this case, you have a value, and a number of different options to try based on the value. A switch statement first specifies what expression is to be compared, and then provides for specific values to look for:
switch (i)
{
case 1:
...
case 2:
...
case 3:
...
}
The program starts executing code from the first case it
matches, and keeps executing until the end of the switch block, or
until it finds a break statement. In this way, multiple
values can be set to execute the same code easily:
switch (i)
{
case 1:
...
break;
case 2:
...
break;
case 3:
case 4:
...
break;
}
One additional label is provided, the default label. If a
default is specified, it is executed if no specific matching
value is found. If no default is found, and no matches exist,
program execution continues following the switch block.
switch (i)
{
case 1:
...
break;
case 2:
...
break;
default:
...
break;
}
There is no rule about what order the case values need to appear
in, nor where the default appears within the switch body. A switch
is more efficient than a series of nested loops, but less
versatile; if statements are able to test ranges of values far
more easily than switches. Knowing which to use in which situation
takes a bit of practice. Switches can also be performed on strings
or other value types.
switch (direction)
{
case "north":
...
break;
case "south":
...
break;
default:
...
break;
}
Loops - general commentstopLoops are a way to repeat a task until some condition is met. Computers are very good at doing repetative tasks. All loops have some control expression, and a loop statement (which can be a block of statements). Care must be taken to ensure that the control expression does eventually terminate, otherwise you get an "infinite loop". While LensC is safeguarded against infinite loops stalling the mud, they can still waste an excessive amount of CPU time, and should be avoided. Two additional statements are available within the body of a loop block. They are break and continue. The break statement breaks out of a loop, with control flowing to the next statement after the loop body. In the case of nested loops, the innermost loop is the one broken out of.
while (loopcondition)
{
...
if (breakcondition)
break;
...
}
The continue statement returns to the top of a loop, skipping past any instructions not yet executed, and checks the loop's control expression again. Like with break, if there are nested loops, only the innermost one is continued through.
while (loopcondition)
{
...
if (continuecondition)
continue;
...
}
While loopsThe first loop construct is the while loop. This loop continues to execute a statment (or block of statements) as long as the control expression evaluates to a non-zero value. while (control expression) statement While loops require that the programmer explicitly change some values inside the body of the loop, so that the control expression changes. If the control expression is zero when the loop is first encountered, the loop statement is never executed.
while (a < 10)
{
...
a += 3;
...
}
int done = 0;
while (!done)
{
...
if (a >= 10)
done = TRUE;
}
Do - While loopsA Do - While loop is similar to a while loop in that the loop continues to execute as long as the control expression is non-zero. The difference is that the code inside a do-while loop is guarunteed to execute at least once before the control expression is evaluated for the first time. do statement while ( control expression); Like a while loop, the programmer must explictly modify some variable within the loop body to ensure that the loop will eventually end.
do a++ while (a<10);
do
{
a += 3;
}
while (a < 20);
For loopsFor loops provide the most control of any looping construct. They allow for the initialization and modification of loop control variables outside the body of the loop. This can make a program much more readable, as the body of the loop is dedicated to the tasks the loop must do. for (initial expression;control expression;modification expression) statement The initial expression is evaluated when the loop is first encountered. It can be used to initialize a counter to any wanted value. The control expression is evaluated before each pass through the loop. If the control expression evaluates to zero initially, the loop body won't be executed at all. The modification expresion executes after the loop body has executed. It is used to alter some variable that needs altering each pass through the loop, often a counter. continueing out of a for loop will still evaluate the modification expression before checking the control expression again.
for (i=0;i < 10;i++)
sendt(ch,"I is "+i+"\r\n");
for (i = 1; i <= 10; i++)
{
...
}
One aspect of a for loop, which is not true for the other loop types, is that you can omit any of the expressions that you don't need to use. Make sure that if you do omit any of the expressions that the loop will still terminate.
for ( ; i>5 ; i++)
{
...
}
for ( ; ; )
{
word = first_word(argument,WORD_FIRST);
argument = first_word(argument,WORD_REST);
if (word == "hello" || string_is_empty(word))
break;
}
ReturnThe return keyword is used to tell a function to return to its caller immediately, without running the rest of the function's code. return;
function test(int i)
{
if (i<=0)
return;
...
}
A function that is defined to return a value must use the return
keyword to indicate what value is to be returned to the caller.
The datatype of the returned value must match the datatype the
function is declared to return.
return expression; A program that is defined to return a value, and does not use the return command will return an invalid value, and risk program termination.
function int minimum(int v1, int v2)
{
if (v1 < v2)
return v1;
else
return v2;
}
End
The end command is used to tell the program to terminate
immediately. This is often useful if some necessary condition
is not met.
end; ... if (a != c) end a; else end b; InvalidateThe invalidate keyword tells the program that a given variable has become invalid, and should not be regenerated when rebuilding the handle table. invalidate self; This is rarely necessary, however, if your program destroys an object or a character, it may no longer reference them. If it attempts to, the program may crash. To prevent the program from terminating, invalidation of the handle is necessary. Back to topThe PreprocessorLensc also includes a basic preprocessor that can be used to handle some text manipulation jobs. Currently, it supports two operations, including another program, and defining text replacements. To specify a preprocessor directive, you must specify the directive at the beginning of a line. The first preprocessor directive is the ability to include the body of another program within your program. This can be useful to write commonly used functions in separate vnums and include those functions within your actual programs. To include the body of another program, you need to know the vnum of that program. You then specify #include <vnum> where you would like that code included. The second preprocessor directive is the ability to make simple text replacements. These are case-sensitive and context-insensitive (meaning, you could potentially replace all occurances of the letter 'a' with 'b' and it would let you - although your program would not compile as a result. Replacements are called defines. To define a replacements, use the syntax: #define <text to replace> <replacement text>. Defines are applied to full identifiers only. So, defining 'foo' will not cause a replacement in the word foobar, only when foo is found alone. Defines are applied throughout the text of the program, including any included text, regardless of where they are defined. A subset of the generic define is the macro. A Macro allows for variables to be inserted within the text replacement. Remember, however, that this is not true variable passing, it's text replacement. Good macros make sure that the parameters are wrapped within parenthesis.
A macro is defined the same way as any other define, however, you
include a variable list with the definition: Defines are very useful for keeping your programs readable. You can define names to be used instead of numbers, allowing people to read meaningful constant names within the programs. CompilingTo compile a lensc program, use the olc compile vnum command. This will either inform you that the program compiled successfully, or produce a list of program errors. Always start fixing errors with the first error listed. Subsequent errors may be the result of the compiler being confused due to the first error. You can use the olc preprocess command to see the output of the preprocessor for a given program as well. If you are experiencing errors that you're having a hard time tracking down, this can be a useful tool for eliminating errors caused by unexpected preprocessor directives. Back to topDebuggingLensc has a primitive debugger installed. This debugger allows you to run a program one line at a time, and watch certain data values as they occur in the program. To start the debugger, you must have the program compiling without errors. If the program does not compile, the debugger will simply generate the messages that the compiler would generate. Having a program compile does not mean that the program works. Debugging is often required to ensure that the program does what you want it to. To debug a program, enter debug vnum Caller argument. The vnum is, of course, the vnum of the program to run. The caller is the object or character which would normally run this program. This item or character can be anywhere on the mud, so be careful with what you enter. The determination of whether the debugger looks for an object or a character is based on the data type of the program running. The program refers to the caller as the self variable. The argument is the remained of the text on the line, and is referenced in the program as the argument variable. Debugger commands:
Learning to use the debugger is a skill of its own. At its simplest, you can see which lines of code are being executed. I suggest debugging all programs completely before allowing them to be run by actions automatically. Back to top |