Maximus BBS

Documentation for Maximus BBS — Next Generation

View on GitHub

Variables & Types

Every data type MEX supports — primitives, strings, arrays, structs, and how to convert between them

Every piece of data in a MEX script lives in a variable, and every variable has a type. MEX is statically typed — the compiler needs to know what kind of data you’re working with before it’ll let you do anything with it. No surprises at runtime because you accidentally added a string to an integer.

If you’re coming from C, the type system will feel familiar with a few Pascal-flavored twists: declarations use a colon instead of prefix notation, arrays have explicit bounds, and strings are dynamic (no buffer sizing, no null terminators, no tears). If you’re not coming from C, don’t worry — the lessons already walked you through most of this by building real things. This page is the complete reference.


On This Page


Quick Reference

Primitive Types

Type Size Range Notes
char 1 byte 0–255 Single character or small unsigned integer
int 2 bytes −32,768 to 32,767 Signed 16-bit integer
unsigned int 2 bytes 0 to 65,535 Unsigned 16-bit integer
long 4 bytes −2,147,483,648 to 2,147,483,647 Signed 32-bit integer
unsigned long 4 bytes 0 to 4,294,967,295 Unsigned 32-bit integer
string dynamic n/a Dynamic-length string — grows and shrinks automatically

Conversion Functions

Function Direction Example
itostr() intstring itostr(42)"42"
uitostr() unsigned intstring uitostr(65000)"65000"
ltostr() longstring ltostr(100000)"100000"
ultostr() unsigned longstring ultostr(3000000)"3000000"
strtoi() stringint strtoi("42")42
strtol() stringlong strtol("100000")100000

Primitive Types

MEX has five primitive types. They’re all integer-family — there are no floating-point numbers. If you need decimal math, you’ll have to fake it with fixed-point arithmetic (multiply by 100, do your math, divide at display time).

char — A single byte. Can hold a character ('A') or a small number (0–255). Characters are unsigned by default. Use char for flags, loop counters over small ranges, and individual keypress values.

int — A signed 16-bit integer. This is the workhorse type for counters, indices, return codes, and most numeric work. Range: −32,768 to 32,767.

unsigned int — The unsigned variant. Range: 0 to 65,535. Used in the user record for fields like usr.priv, usr.times, and usr.credit.

long — A signed 32-bit integer. Use for message numbers, file sizes, timestamps, and anything that might exceed 32,767.

unsigned long — The unsigned variant. Range: 0 to 4,294,967,295. Used for usr.up, usr.down, time(), and byte counts.

string — A dynamic-length character string. No fixed buffer, no null terminator to manage. Strings grow when you concatenate, shrink when you reassign, and the runtime handles all the memory. See Strings for the full story.


Declaration Syntax

MEX declarations use a colon between the type and the variable name — a Pascal convention that reads naturally once you get used to it:

int: count;
string: name;
long: file_size;

Multiple variables of the same type on one line:

int: i, j, k;
string: first, last;

All numeric variables are initialized to 0. All strings are initialized to "" (the empty string). You don’t have to set them before use, but it’s good practice.

Assignment uses := (not = — that’s comparison):

count := 10;
name := "Sysop";
file_size := 1024 * 1024;

Characters & Escape Sequences

Character literals use single quotes: 'A', '7', '\n'. You can also use hex values for CP437 characters: 0xCD for the double horizontal line ().

Escape Sequences

Sequence Value Meaning
\n 10 Newline (line feed)
\r 13 Carriage return
\t 9 Tab
\b 8 Backspace
\f 12 Formfeed (clears screen)
\\ 92 Literal backslash
\" 34 Literal double quote
\a 7 Bell (beep)

These work in both character literals ('\n') and string literals ("Hello\n").


Strings

Strings are dynamic. You never declare a buffer size — just use them:

string: greeting;
greeting := "Welcome to " + prm_string(0) + "!";

Concatenation uses the + operator:

string: full_name;
full_name := usr.name + " (" + usr.alias + ")";

Character indexing uses [] with 1-based positions:

string: s;
char: ch;

s := "Hello";
ch := s[1];      // ch = 'H'
s[5] := '!';     // s = "Hell!"

String length with strlen():

if (strlen(usr.city) = 0)
  print("You haven't set your city yet.\n");

The full set of string functions is covered in String Operations.


Type Conversions

MEX does not implicitly convert between numeric types and strings. If you pass an int where a string is expected, the compiler will reject it. You need explicit conversion functions.

This is the gotcha from Lesson 3 — if your function takes a string parameter and you hand it usr.times (an unsigned int), you need uitostr():

print("Calls: " + uitostr(usr.times) + "\n");

itostr(int) → string

Converts a signed int to its string representation.

string: s;
s := itostr(-42);   // s = "-42"

uitostr(unsigned int) → string

Converts an unsigned int to its string representation.

print("Priv level: " + uitostr(usr.priv) + "\n");

ltostr(long) → string

Converts a signed long to its string representation.

print("Messages posted: " + ltostr(usr.msgs_posted) + "\n");

ultostr(unsigned long) → string

Converts an unsigned long to its string representation.

print("Downloaded: " + ultostr(usr.down) + " KB\n");

strtoi(string) → int

Parses a string as a signed int. Returns 0 if the string can’t be parsed.

int: num;
num := strtoi("42");

strtol(string) → long

Parses a string as a signed long.

long: big_num;
big_num := strtol("1000000");

Numeric Casts

For conversions between numeric types (e.g., int to long), use a C-style cast:

long: big;
int: small;

small := 42;
big := (long)small;

Arrays

Arrays use a Pascal-style declaration with explicit lower and upper bounds:

array [1..100] of int: scores;
array [0..9] of string: names;

The bounds are inclusive — [1..100] gives you 100 elements, indexed 1 through 100. The lower bound doesn’t have to be 1 (or even 0), though 0-based arrays generate slightly faster code.

Accessing elements:

scores[1] := 95;
scores[2] := 87;
print("First score: " + itostr(scores[1]) + "\n");

Looping over an array:

int: i;
for (i := 1; i <= 100; i := i + 1)
  scores[i] := 0;

Out-of-bounds access is undefined behavior — no runtime check, no error message, just garbage. Be careful with your indices.

Arrays of structs work too — this is how you build lightbar menus:

array [1..5] of string: items;
items[1] := "Read Messages";
items[2] := "File Areas";
items[3] := "Goodbye";

Structs

Structs group related data into a single variable. They’re declared with the struct keyword:

struct _stamp
{
  struct _date: date;
  struct _time: time;
};

Access fields with the dot operator:

struct _stamp: now;
timestamp(now);
print("Hour: " + itostr(now.time.hh) + "\n");

You typically won’t define your own structs — the headers (max.mh, maxui.mh) declare all the struct types you need. But you will use them constantly, especially the user record and the style structs for UI widgets.

Passing structs to functions — structs are passed by value by default. Use ref if the function needs to modify the struct or if the struct is a style that gets filled in by a default-initializer:

struct ui_edit_field_style: es;
ui_edit_field_style_default(es);   // takes ref, fills in defaults

The User Record (usr)

The usr global is the most-used struct in MEX. It’s a struct _usr that contains everything Maximus knows about the current caller. It’s always available — no function call needed.

Identity & Contact

Field Type Description
usr.name string Caller’s real name
usr.alias string Caller’s alias/handle
usr.city string City and state/province
usr.phone string Voice phone number
usr.dataphone string Data phone number
usr.pwd string Password (encrypted if usr.encrypted is set)
usr.dob string Date of birth (yyyy.mm.dd)
usr.sex char SEX_UNKNOWN (0), SEX_MALE (1), SEX_FEMALE (2)

Activity & Stats

Field Type Description
usr.times unsigned int Total previous calls
usr.call unsigned int Calls today
usr.msgs_posted long Messages posted (lifetime)
usr.msgs_read long Messages read (lifetime)
usr.up unsigned long KB uploaded (lifetime)
usr.down unsigned long KB downloaded (lifetime)
usr.downtoday unsigned long KB downloaded today
usr.nup unsigned long Files uploaded (lifetime)
usr.ndown unsigned long Files downloaded (lifetime)
usr.ndowntoday unsigned long Files downloaded today
usr.time unsigned int Minutes online today

Preferences & Terminal

Field Type Description
usr.priv unsigned int Privilege level
usr.help char Help level: HELP_NOVICE (6), HELP_REGULAR (4), HELP_EXPERT (2)
usr.video char Terminal: VIDEO_TTY (0), VIDEO_ANSI (1), VIDEO_AVATAR (2)
usr.width char Screen width
usr.len char Screen height
usr.hotkeys char 1 = hotkeys enabled
usr.fsr char 1 = full-screen reader enabled
usr.more char 1 = “More?” prompts enabled
usr.cls char 1 = can handle clear-screen
usr.ibmchars char 1 = can handle IBM (high-bit) characters
usr.rip char 1 = RIP graphics enabled
usr.tabs char 1 = can handle tab characters
usr.bored char 1 = BORED editor, 0 = MaxEd
usr.lang char Current language index
usr.def_proto signed char Default protocol (see PROTOCOL_* constants)
usr.compress char Default compression program

Dates & Expiration

Field Type Description
usr.ludate struct _stamp Date/time of last call
usr.date_1stcall struct _stamp Date of first call
usr.date_pwd_chg struct _stamp Date of last password change
usr.date_newfile struct _stamp Date of last new-files search
usr.xp_date struct _stamp Subscription expiration date
usr.xp_mins unsigned long Minutes remaining before expiration
usr.xp_priv unsigned int Privilege to demote to on expiration
usr.expdate char 1 = expire based on date
usr.expmins char 1 = expire based on minutes
usr.expdemote char 1 = demote on expiration
usr.expaxe char 1 = hang up on expiration

Other

Field Type Description
usr.lastread_ptr unsigned int Offset in lastread database
usr.xkeys string Access keys (as a string)
usr.credit unsigned int NetMail credit (cents)
usr.debit unsigned int NetMail debit (cents)
usr.point_credit unsigned long Point credits
usr.point_debit unsigned long Point debits
usr.time_added unsigned int Bonus time credits for today
usr.msg string Current message area tag
usr.files string Current file area tag
usr.deleted char 1 = user record is deleted
usr.permanent char 1 = user is permanent (never expires)
usr.notavail char 1 = not available for chat
usr.nerd char 1 = cannot yell for sysop
usr.noulist char 1 = hidden from user list
usr.badlogon char 1 = last logon attempt was bad
usr.configured char 1 = registration fields filled in
usr.nulls char Number of NUL characters after CR

Global Data

Beyond usr, MEX exports several other global variables that are always available:

Variable Type Description
input string Current stacked input string
id struct _instancedata Session info: task number, baud rate, local flag
marea struct _marea Current message area (name, path, tag, attributes)
farea struct _farea Current file area (name, paths, attributes)
msg struct _msg Current message (number, high, count, direction)
sys struct _sys System info (cursor position, more-line count)

These are read-only snapshots — modifying marea.name doesn’t change the actual area. Use the area selection functions (msgareaselect, fileareaselect) to actually change areas.


See Also