BSL:Introduction: Difference between revisions

From OniGalore
Jump to navigation Jump to search
(added comments and syntax sections; added some bold text to emphasize certain examples; a lot of this material is about to get rearranged)
m (wording and section link improvements; don't link to the unfinished BSL listing project)
 
(13 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{TOCfloat}}
This page gives a brief look at [[BSL]], Oni's scripting language. For those with scripting or programming experience, BSL's syntax and concepts will be very familiar, but BSL's feature set is simpler than most scripting languages. For details on any aspect of the language, as well as documentation of potentially-serious quirks in certain features of the language, read the full [[BSL:Manual|BSL Manual]].
Like any typical scripting or programming language, BSL scripts consist of [[#Comments|commented]] [[#Files|plain-text files]] which use [[#Logic|branching logic]] and [[#Operators|various operators]] according to a [[#Syntax|strict syntax]] to process [[#Types|data]] with [[#Functions|functions]] that act upon [[#Variables|variables]]. Click one of those words to jump to the brief explanations of those terms below, but if you are new to programming, you'll also want to read the "Details" pages that are linked to.
{{TOClimit|2}}
 
For those familiar with C, the syntax is similar and you will feel at home in BSL, but BSL's feature set is stripped down to a degree that is simpler than even most scripting languages. Fortunately, this makes BSL fast to learn.
{{clearall}}
 
==Comments==
Comments are notes from the programmer to explain some code. They are supposed to be placed above or after a line of code:
 
'''# The following block of code calculates the meaning of life'''
if [block of code begins here]
 
var int a = 0; '''# this global is also modified in my_cutscenes.bsl'''
 
In documentation outside of source code or script files, such as this page, they are still sometimes used to tell the reader something in a way that doesn't break the actual code, if the user should type it in exactly as it appears, comments and all.
 
==Files==
==Files==
When Oni loads a level, it also loads and parses all .bsl files in the folder in [[IGMD]] which contains that level's scripts (the name of the folder being specified by the level's [[ONLV]] resource). Like C, the code automatically begins running with the function called "main", regardless of which .bsl file it is found in.
When Oni loads a level, it also loads and parses all .bsl files in the folder in [[IGMD]] which contains that level's scripts (the name of this folder being specified by the level's [[ONLV]] resource). Like C, the code automatically begins running with the function called "main" at the time of level-load, regardless of which .bsl file it is found in (the convention in Oni's scripts is to place it in a file called *_main.bsl).
 
From that point on, only other functions that are called by main() will run on their own, though you can also manually call any of those functions from the [[Developer Mode|developer console]] (however, you cannot actually define variables or functions through the console). Thus, to see how the script for a level will flow, you must locate the main() function and read from there.


Note that the optional [[global]] folder is also loaded for all levels, but this code cannot run on its own unless you edit another function that does run automatically so that it calls a function in a global BSL file.
Unlike C, there are other "entry points" in the code. For instance, characters can call script functions upon death and trigger volumes can call functions when they are entered or exited. See [[CHAR]], [[OBD:BINA/OBJC/CONS|BINA CONS]], [[OBD:BINA/OBJC/DOOR|BINA DOOR]], [[NEUT]], and [[TRGV]] for all known places in the game data that can trigger BSL functions.


==Logic==
The optional [[IGMD/global|global]] folder is also loaded for all levels, but any function found in a .bsl file in global/ will be stranded, only accessible by [[Developer Mode|developer console]], unless it is called by a function in a level script or by one of the above types of game data in that level.
:''See [[BSL:Statements]] for details.''
In order to react to various conditions, you need branching logic, which basically boils down to an if-else statement in this form:
 
if (value1 operator value2)
{
   
}
else
{
   
}
 
To explain the above:
*The arrangement of braces and whitespace is somewhat flexible, but this is the standard style used in most places and is considered to be the most readable style.
*You may not need the "else" statement, in which case simply omit it.
*<tt>value1</tt> and <tt>value2</tt> can be variables or constants. <tt>operator</tt> refers to one of the relational operators in BSL (see "Operators" below). A typical "if" statement would look like...
 
if (counter eq 3) # "if counter's value is equal to three, do this..."
{
    # some code here
}
 
...where counter is a variable declared beforehand (see "Variables" below). You can also test multiple conditions at once in an "if" statement.
 
==Operators==
:''See [[BSL:Operators]] for details.''
We're all familiar with the four basic arithmetical operations: addition, subtraction, multiplication and division. Well, BSL only has the first two :-) They're the "+" and "-" symbols, as you would expect. Example:
 
counter = counter + 1; # increases counter's value by one
 
To compare two values, you use relational operators, like this:
 
if (counter < 3)


==Syntax==
==Syntax==
:''See [[BSL:Syntax]] for details.''
===Statements===
 
BSL offers the choice of a strong and weak syntax in some contexts. Here's a typical statement…
BSL allows two kinds of syntax to be used somewhat interchangeably, which can be called "shell-style" and "C-style". This can be confusing; it's recommended to stick to C-style consistently in your scripting, because it's more explicit and therefore safer.


Braces define blocks of code, whether the block is a function or a conditional:
dmsg("Hello");


func void some_function(void)
…but you can omit the semicolon, parentheses, and also the quotes if your string doesn't have any spaces in it:
'''{'''
    # some code
'''}'''


  if (condition)
  dmsg Hello
'''{'''
    # some code
'''}'''


Semicolons end a statement and parentheses enclose function parameters, but only in C-style BSL:
Similarly, here's the formal syntax for a snippet of code:


  func void some_function(void)
  func void some_function(void)
Line 81: Line 25:
     a = 4;
     a = 4;
     sleep(60);
     sleep(60);
     if (something)
     if (a eq 4)
     {
     {
       # some code
       dprint("Hello");
     }
     }
  }
  }


The same code would look like this in shell-style BSL:
The same code would look like this in the weak syntax:


  func some_function
  func void some_function(void)
  {
  {
     var int a
     var int a;
     a = 4
     a = 4
     sleep 60
     sleep 60
     if (something)
     if (a eq 4)
     {
     {
       # some code
       dprint "Hello"
     }
     }
  }
  }


Notice that parentheses are always needed with "if" statements, and also note that the type (see "Types" below) of the function and its parameters can be left out in shell-style BSL (this meaning "void" implicitly), but it doesn't have to be.
Notice that parentheses are always needed with "if" statements and semicolons are always needed on variable declarations. You will probably find the strong syntax to be more consistent and safe to use than the weak syntax. Be aware that that mixing strong and weak syntax in one file can cause errors (see {{SectionLink|BSL:Manual|Old vs. new syntax}} for details).


==Types==
===Comments===
:''See [[BSL:Types]] for details.''
Comments are marked with the pound sign:


When declaring a variable or a function, you must specify the type of data it contains or returns:
var int a = 0; # this global is also modified in my_cutscenes.bsl


var int x;
==Reserved words==
func int my_func(void)
===Declaration===
{...}
You always need to use "var" when declaring a variable and "func" when defining a function:


You can choose from "bool" (can be true/false or 1/0), "int" (can be any whole number), "float" (a value with a decimal point), or "string" (some text).
var int a = 0;
func int get_enemy_count(void)


==Functions==
Functions do not need to be defined or declared above the place in the code where they are called, unlike in C.
:''See [[BSL:Functions]] for details.''
 
Unlike C, functions do not need to be declared or defined before they are called. Functions are defined by starting with the keyword "func", then giving the type of data (see "Types" above) that the function returns, then the name of the function. After that, any parameters it receives should be listed within parentheses. Finally, the actual code for the function should be given within curly braces, like so:
===Type specification===
BSL does not support weak typing. You need to specify the type when making declarations, as seen in the above examples. You can choose from "bool" (buggy, so not recommended), "int", "float" (rarely useful because of BSL's limited math operations), and "string". As with C, you use "void" to mean "no type" when defining functions.
 
===Conditional===
BSL supports standard if/else statements, and you can use "and" and "or" to test compound conditions:


  func int my_awesome_function(int some_input)
  if ((a < b) and (c > d))
{
    ...
}
else if (a < 0)
  {
  {
     var some_output = some_input + 5;
     ...
    return some_output;
  }
  }
else
{
    ...
}
Beware of a bug in BSL where variable assignments under an "if" statement will fire even when the "if" condition is false. See {{SectionLink|BSL:Manual|Conditional}} for details.
===Flow interrupt===
There is no "goto" statement in BSL, nor any loop controls like "continue" or "break" (since there are no proper loops!). There's a "return" keyword in BSL, but a "return" inside of an "if" statement will fire regardless of whether the statement evaluates to true or false. Thus you can only use "return" to return data at the end of the function, not to exit early (see {{SectionLink||Functions}}).
There's also a "sleep" command that pauses BSL execution; you pass it a number in ticks:
sleep(60); # wait for one second


Now that it is defined, we can call it from anywhere in the BSL for the same level by using this invocation:
===Loop===
There's no loop keyword like "for" or "while" in BSL, but you can create a loop using one of two methods. First, you can call a function recursively, but BSL has a short stack so don't expect to get more than four levels deep. You can avoid recursion and the related stack limitation by "sleep"ing for a tick or more, and then "fork"ing the call that would otherwise be recursive. See [[BSL:Snippets]] for an example that also makes up for the missing multiplication operator in BSL.


var result = my_awesome_function(counter); # sets "result" to the the value of "counter" plus 5
Second, you could use schedule-repeat-every:


Note that (1) we can set a variable to the result (see "Variables below) and then use that value elsewhere, and (2) the name of the variable we pass in to the function does not have any relation the name that the function uses for that value internally. That's because we might simply desire to pass a constant number to the function:
schedule some_function() repeat 50 every 20;


var result = my_awesome_function(3);
...or schedule-at:


No matter what, the value passed in will be called "some_input" inside the function.
schedule some_function() at 15;
schedule some_function() at 30;
schedule some_function() at 45;
...


==Variables==
Just be aware that the BSL will continue executing without waiting for this "loop" to finish.  Also, there is no way to exit the "loop" early, but some_function() could simply refuse to perform its task if a global variable has changed to false, simulating a loop exit.
:''See [[BSL:Variables]] for details.''
 
===Multithreading===
BSL doesn't have a robust solution, but you can perform a certain amount of multithreading using "fork" or "schedule". Please see {{SectionLink|BSL:Manual|Concurrency}} for examples.


You've already seen some example of variable declaration. One more thing to be aware of is that variables can be initialized upon declaration:
==Operators==
Like some other languages, BSL differentiates between ''checking for'' equivalency ("eq") and ''setting'' equivalency ("="):


  var int y '''= 9''';
  if (counter eq 3) ...
i = 4;


or not:
BSL's operators are pretty standard stuff:
+ -
eq ne < > <= >=
and or !


var int y;
But you'll note that there is no operator for multiplying or dividing! You'll need to create your own loop with addition and subtraction to perform that kind of math.


In the second case, "y" is set automatically to zero.
==Data types==
You can choose from "void", "bool", "int", "float", and "string" (but "void" is only allowed when defining a function). The "bool" type is buggy in Windows Oni, so use of "int" is recommended instead.


When declaring variables, it's important to be aware of scope. The scope is the range of the BSL script within which the variable can be referred to. If a variable is declared with global scope...
var int a = 1;
# see the "Functions" section below on how to use a data type keyword when defining a function


func some_function
The "int" type is signed 32-bit. See {{SectionLink|BSL:Manual|Data types}} to learn about various limitations on math between different data types in BSL.
{...}<br>
var int y = 0;<br>
func another_function
{...}


...then it can be accessed from both <tt>some_function</tt> and <tt>another_function</tt>, or any other function in the level's BSL files, for that matter. If it is declared with function scope...
==Functions==
As mentioned above, functions do not need to be declared or defined before they are called. Defining and calling a function looks exactly like C except for the "func" keyword in the definition:


  func another_function
a = get_enemy_count(0);<br />
  func int get_enemy_count(int count_dead)
  {
  {
     var int y = 0;
     ...
    return(count);
  }
  }


...then it can only be accessed within that function. If it is declared with block scope...
Once again, please see {{SectionLink|BSL:Manual|Functions}} section to learn about concurrent and recursive calling.
 
==Variables==
As usual, variables can be explicitly initialized:
 
var int y = 9;
 
or not:
 
var int y;
 
In the second case, "y" is automatically initialized to zero.


func another_function
Variables will have global scope if declared outside of a function.
{
    if (...)
    {
      var int y = 0;
      ...
    }
}


...then only other code inside the "if" block can access it.
==Built-in functions and variables==
Like any game, Oni provides access to a number of hardcoded functions and global variables that can be used to alter the game environment. They are listed on [[BSL:Functions]] and [[BSL:Variables]].


[[Category:BSL syntax]]
[[Category:BSL docs]]

Latest revision as of 00:10, 4 February 2024

This page gives a brief look at BSL, Oni's scripting language. For those with scripting or programming experience, BSL's syntax and concepts will be very familiar, but BSL's feature set is simpler than most scripting languages. For details on any aspect of the language, as well as documentation of potentially-serious quirks in certain features of the language, read the full BSL Manual.

Files

When Oni loads a level, it also loads and parses all .bsl files in the folder in IGMD which contains that level's scripts (the name of this folder being specified by the level's ONLV resource). Like C, the code automatically begins running with the function called "main" at the time of level-load, regardless of which .bsl file it is found in (the convention in Oni's scripts is to place it in a file called *_main.bsl).

Unlike C, there are other "entry points" in the code. For instance, characters can call script functions upon death and trigger volumes can call functions when they are entered or exited. See CHAR, BINA CONS, BINA DOOR, NEUT, and TRGV for all known places in the game data that can trigger BSL functions.

The optional global folder is also loaded for all levels, but any function found in a .bsl file in global/ will be stranded, only accessible by developer console, unless it is called by a function in a level script or by one of the above types of game data in that level.

Syntax

Statements

BSL offers the choice of a strong and weak syntax in some contexts. Here's a typical statement…

dmsg("Hello");

…but you can omit the semicolon, parentheses, and also the quotes if your string doesn't have any spaces in it:

dmsg Hello

Similarly, here's the formal syntax for a snippet of code:

func void some_function(void)
{
   var int a;
   a = 4;
   sleep(60);
   if (a eq 4)
   {
      dprint("Hello");
   }
}

The same code would look like this in the weak syntax:

func void some_function(void)
{
   var int a;
   a = 4
   sleep 60
   if (a eq 4)
   {
      dprint "Hello"
   }
}

Notice that parentheses are always needed with "if" statements and semicolons are always needed on variable declarations. You will probably find the strong syntax to be more consistent and safe to use than the weak syntax. Be aware that that mixing strong and weak syntax in one file can cause errors (see BSL:Manual § Old vs. new syntax for details).

Comments

Comments are marked with the pound sign:

var int a = 0; # this global is also modified in my_cutscenes.bsl

Reserved words

Declaration

You always need to use "var" when declaring a variable and "func" when defining a function:

var int a = 0;
func int get_enemy_count(void)

Functions do not need to be defined or declared above the place in the code where they are called, unlike in C.

Type specification

BSL does not support weak typing. You need to specify the type when making declarations, as seen in the above examples. You can choose from "bool" (buggy, so not recommended), "int", "float" (rarely useful because of BSL's limited math operations), and "string". As with C, you use "void" to mean "no type" when defining functions.

Conditional

BSL supports standard if/else statements, and you can use "and" and "or" to test compound conditions:

if ((a < b) and (c > d))
{
   ...
}
else if (a < 0)
{
   ...
}
else
{
   ...
}

Beware of a bug in BSL where variable assignments under an "if" statement will fire even when the "if" condition is false. See BSL:Manual § Conditional for details.

Flow interrupt

There is no "goto" statement in BSL, nor any loop controls like "continue" or "break" (since there are no proper loops!). There's a "return" keyword in BSL, but a "return" inside of an "if" statement will fire regardless of whether the statement evaluates to true or false. Thus you can only use "return" to return data at the end of the function, not to exit early (see § Functions).

There's also a "sleep" command that pauses BSL execution; you pass it a number in ticks:

sleep(60); # wait for one second

Loop

There's no loop keyword like "for" or "while" in BSL, but you can create a loop using one of two methods. First, you can call a function recursively, but BSL has a short stack so don't expect to get more than four levels deep. You can avoid recursion and the related stack limitation by "sleep"ing for a tick or more, and then "fork"ing the call that would otherwise be recursive. See BSL:Snippets for an example that also makes up for the missing multiplication operator in BSL.

Second, you could use schedule-repeat-every:

schedule some_function() repeat 50 every 20;

...or schedule-at:

schedule some_function() at 15;
schedule some_function() at 30;
schedule some_function() at 45;
...

Just be aware that the BSL will continue executing without waiting for this "loop" to finish. Also, there is no way to exit the "loop" early, but some_function() could simply refuse to perform its task if a global variable has changed to false, simulating a loop exit.

Multithreading

BSL doesn't have a robust solution, but you can perform a certain amount of multithreading using "fork" or "schedule". Please see BSL:Manual § Concurrency for examples.

Operators

Like some other languages, BSL differentiates between checking for equivalency ("eq") and setting equivalency ("="):

if (counter eq 3) ...

i = 4;

BSL's operators are pretty standard stuff:

+ -
eq ne < > <= >=
and or !

But you'll note that there is no operator for multiplying or dividing! You'll need to create your own loop with addition and subtraction to perform that kind of math.

Data types

You can choose from "void", "bool", "int", "float", and "string" (but "void" is only allowed when defining a function). The "bool" type is buggy in Windows Oni, so use of "int" is recommended instead.

var int a = 1;
# see the "Functions" section below on how to use a data type keyword when defining a function

The "int" type is signed 32-bit. See BSL:Manual § Data types to learn about various limitations on math between different data types in BSL.

Functions

As mentioned above, functions do not need to be declared or defined before they are called. Defining and calling a function looks exactly like C except for the "func" keyword in the definition:

a = get_enemy_count(0);
func int get_enemy_count(int count_dead) { ... return(count); }

Once again, please see BSL:Manual § Functions section to learn about concurrent and recursive calling.

Variables

As usual, variables can be explicitly initialized:

var int y = 9;

or not:

var int y;

In the second case, "y" is automatically initialized to zero.

Variables will have global scope if declared outside of a function.

Built-in functions and variables

Like any game, Oni provides access to a number of hardcoded functions and global variables that can be used to alter the game environment. They are listed on BSL:Functions and BSL:Variables.