Skip to content

Mjs script syntax

Robert Jordan edited this page May 5, 2021 · 23 revisions

Mjs script syntax and grammar

The uncompiled .mjs script syntax looks like a fusion between C and JavaScript.

Identifiers

Prefixes and postfixes

Prefixes and postfixes are essential (and mandatory) for identifiers.

Prefix Identifiers Postfix Types
Identifies Storage
$ function -
# variable persistent
@ variable savefile
% variable thread
_ variable local
invalid -
Type
Int
% Float
$ String
# IntArray
%# FloatArray
$# StringArray

Groups

Groups act as namespaces for identifiers. A group is defined in-script with the following line:

#group "<NAME>"
Group Scope
@ local
@GLOBAL global
@MAJIRO_INTER system calls
@<NAME> user-defined

Identifier structure

S Identifier T Group
$ handle_float % @GLOBAL

Comments

// line comment
/* block
comment */

Operators

Note: The following operators have never been observed in-script, but are known to exist: %, ^, ~, ++x, --x

Note: It's unclear whether ternary operators require arguments to be surrounded with ().

// Unary:
x--; x++; --x; ++x; !x; -x; ~x; //unknown: +x;

// Binary:
x+y; x-y; x*y; x/y; x%y; x&y; x|y; x^y;
x&&y; x||y; x==y; x!=y; x<y; x<=y; x>y; x>=y;

// Assignment:
x=y;
// Compound:
x+=y; x-=y; x*=y; x/=y; x%=y; x&=y; x|=y; x^=y;

// Ternary:
(y)?(a):(b);

Preprocessor

#include "flags.txt"
#include "includes\stdio.mjh"
#group "MYNAMESPACE"
#group push|pop             // Push or pop current group from stack, allowing to temporarily overwrite the current group (used in headers)
#forcecr on|off             // If you put this in, line breaks will be normal (force carriage return?)
#forcegr "$foo();", "$bar$baz$"  // Possibly defines a list of functions that will automatically trigger the "$foo()" statements. "gr" may be shorthand for "graphics"
#forcewipeonblank on|off    // The last line is \p \w automatically
#subst "s/pattern/repl/"    // Regex replace macro, uses "\#"-style replacement and not "$#" (quotes do not end string until the '/' character)
#use_readflg on|off         // This source is judged as read (likely affects second field in .mjo script format)
#define MY_MACRO  0         // Comments can appear with defines
#if
//#elif   // usage unknown
#else
#endif

Keywords

Note: There also exists instructions to handle ranges in switch statements, so it's possible there's a related keyword to support this (such as range, which has been observed in the CatSystem2 engine).

if, else if, else
for, while, do while
switch, case, default, break, unbreak
return, goto
void, func, var
setskip, constructor, destructor

See Special block syntaxes for explanation on setskip, constructor, destructor keywords.

Note: It's unknown if it's legal to place expressions for inline branching/looping statements on a separate line.

Branching keywords

// Branching:
if(x) {
} else if(y) {
} else { }

// Inline Branching:
if(a)      x;
//else if(b) y; // unknown
//else       z; // unknown

Looping keywords

// Looping:
while(a) { }
do { } while(a);
for(i=0; i<10; i++) {}

// Inline Looping:
while(a) x;
//for(i=0; i<10; i++) y; // unknown

Switch keywords

// Switches:
switch(a) {
case -1:
  break; // exit switch
case 2:
  unbreak; // fallthrough to next case
case 3:
  x;
  // breaking is optional, no-break behavior is the same as including a break at the end of the case
default:
  // default cases supported
}

switch(s$) { // switches allow string and potentially float arguments too
case "#1":
  switch (b) { // switch statements can be nested
  case 77:
  case 88:
  }
  break;
case "#2":
}

Labels/Return keywords

my_label:
  goto my_label;

return(x); // unknown if `()` are required
return;    // return for void function (in MjIL, this equates to `return(0);`

Definition keywords

void $myfunc_noret();
func $myfunc_withret();
var myvar, anothervar; // inline declaration with assignment is not allowed

Special variables

__SYS__NumParams // number of arguments passed to function

Declarations

Variable declaration

All user-variables are required to be defined ahead of time, in similar fashion to C. Though they do not need to appear immediately at the beginning of a function, just before their usage. Variables probably do not support declarations with assignment, which includes for declarations within for loops.

var _onevar, _morevar;
var _local_var;       // value is stored in stack-frame
var %threadlocal_var; // value is stored in thread
var @savefile_var;    // value is stored in save-game
var #persistent_var;  // value is stored globally, and persists even when rebooting the game

Function declaration

Function declarations also follow similar patterns to C. The void argument seems to be mandatory when no argument's exist.

void $function_with_no_args(void) {
}
void $function_with_args(_arg1, _another_arg, _str_arg$) {
}
func $function_returns_int(void) {
}
func $function_returns_string$(void) {
}
func $function_returns_floatarray%#(void) {
}
// Optionals are defined with [], and can appear before or after a comma
func $function_with_optionals(_page[, _xin, _yin]) {
}
func $function_with_optionals2(_page, [_xin, _yin]) {
}

Function forward declaration

// forward-declared function contained within same script
func $check_font_change_only(void);

// forward-declared functions from pic.mjo
func $picpic_shortfix$@PIC(_fn$);
void $pic_unpack_zyouzan@PIC(_to, _fn$[, _xin, _yin]);

// forward-declared function from pic.mjo (within a function)
void $ame_stop@PIC();
$ame_stop@PIC(); // called immediately afterwards

Special block syntaxes

There are 3 identified types of special blocks seen in Majiro source scripts.

constructor

The constructor acts as an initialization for the function/thread(?) it's defined in. Usage of this block is not fully understood, as it's only appearance in available source scripts is immediately at the beginning of a function. If there are opcodes used specifically for the constructor, it's possible they are not required if the constructor is immediately run. This block has only ever been observed in a function also containing a destructor block, however its likely they can be used independently if there is no required cleanup.

constructor {
  //...
}

destructor

The destructor block acts as a cleanup method for the function/thread(?) it's defined in. A destructor only becomes active once execution in the function reaches the block. After which, it will be called during thread cleanup. There is potential that defining more than one destructor in a single function is supported, but any new destructor block would replace the old block, causing the old to never execute. There is one opcode dedicated to the destructor's functionality.

destructor {
  //...
}

setskip

This is a VN-related syntax. It's usage isn't fully understood, but it acts as some sort of loop and contains many type of wait executions. There are multiple opcodes used specifically for this block's functionality.

setskip {
  //...
}

Unknown syntaxes

This section describes syntaxes that are known to exist through opcode instructions, but have never been observed in scripts.

Array syntax

Arrays can have 1, 2, or 3 dimensions. When an array has less than 3 dimensions, its treated as if the remaining dimensions are 1 internally. Arrays have no method for determining their lengths, so consider it similar to how heap-allocated arrays are handled in C.

Out-of-bounds access acts based on wrapping, including both positive and negative indices. So accessing index [-1] will access element [N-1], and accessing index [N+4] would access element [4] (assuming N > 4).

Note: How each dimension is accessed in relation to script syntax is completely unknown. The main confusion is how the $dim_create# syscalls take optional arguments in reverse: func $dim_create#([[_dim3,] dim2,] dim1);. In addition the ldelem and stelem instructions take in index operands in the same order as syscalls.

So there's 3 likely possible scenarios:

  • array[,,] access assembles arguments in the same reversed order as calls, and has the most-significant index first (array[z, y, x]):
    • Array: [x=2, y=3, (z=1)]
    • Alloc: ((z), y, x) : $dim_create#(3, 2) -> ldc.i 2; ldc.i 3; syscall $dim_create# (2);
    • Access: [(z), y, x] : array[2, 1] -> ldc.i 1; ldc.i 2; ldelem dim2 ...;
  • $dim_create#() is generated from a keyword such as the new[] operator in-script:
    • Array: [x=2, y=3, (z=1)]
    • Alloc: [x, y, (z)] : new[2, 3] -> $dim_create#(3, 2) -> ldc.i 2; ldc.i 3; syscall $dim_create# (2);
    • Access: [x, y, (z)] : array[1, 2] -> ldc.i 1; ldc.i 2; ldelem dim2 ...;
  • Everything is exactly as it seems, and the script writers are meant to suffer:
    • Array: [x=2, y=3, (z=1)]
    • Alloc: ((z), y, x) : $dim_create#(3, 2) -> ldc.i 2; ldc.i 3; syscall $dim_create# (2);
    • Access: [x, y, (z)] : array[1, 2] -> ldc.i 1; ldc.i 2; ldelem dim2 ...;
myarray# = $dim_create#(3, 2); // allocate int[3, 2] with implied dimensions [[1], 3, 2]

tmparray# = $dim_copy#(myarray#); // clone `myarray#` and return the new instance
Clone this wiki locally