- Introduction
- Hello World
- Data Types
- Statements
- Variables
- Basics
- Functions
- Imports
- Standard Library
- Other Functions
There are 3 major B.x languages:
- B++
- B*
- B#
B++ is the original version, and was very primitive, with no multiline if statements and loops.
B* is to B++ as C++ is to C. All B++ code is valid B* code, but there are additional features, like loops, multiline if statements, try/catch, and many more functions.
B# is not compatible with B++, but introduces features like type-checking, scoping, and more structural changes. These are the B# docs.
In B#, everything is a tag. Tags are denoted with brackets. The syntax is
[NAME arg1 arg2 ...]
To print "Hello, World!" in B#, simply do
[PRINT "Hello, World!"]
Comments can be done with #
.
# This is a comment
There can be inline comments by surrounding code with #
.
There are 7 data types in B#:
Type | Description |
---|---|
INT |
Represents an integer. |
BYTE |
Represents a byte, or a single character of a string. |
FLOAT |
Represents a floating-point number (decimal). |
BOOL |
Represents a value of either true or false. |
STRING |
Represents textual data. |
ARRAY |
Represents multiple values in a list. |
MAP |
Represents key-value data. |
FUNCTION |
Represents a pointer to a function. |
STRUCT |
Represents key-value data but with a fixed set of keys. |
ANY |
Represents a value of any type. |
There are two types of numbers: integers, and floats. Integers represent whole numbers, whereas floats represent decimals. They can both be negative.
[PRINT [STRING 1234]]
[PRINT [STRING 1.234]]
The STRING
function can be used to convert an integer or a float to a string, which can be printed.
A boolean can be one of two values: true
, or false
. The COMPARE
function can be used to get booleans. For example,
[COMPARE 1 == 0]
would be false.
TRUE
and FALSE
are constants.
The compare operators are:
Operator | Description |
---|---|
== |
Tests whether two values are equal. |
!= |
Tests whether two values are not equal. |
> |
Tests whether the left value is greater than the right value. |
< |
Tests whether the left value is less than the right value. |
>= |
Tests whether the left value is greater than or equal to the right value. |
<= |
Tests whether the left value is less than or equal to the right value. |
Logical operations can also be performed on booleans.
[AND <a> <b>] # Returns whether both <a> and <b> are true
[OR <a> <b>] # Returns whether <a> or <b> is true
[NOT <a>] # If <a> is true, it returns false, if <a> is false, it returns true
Strings represent textual data. They are surrounded by quotation marks.
[PRINT "Hello, World!"]
Arrays represent a list of items. Their types are made by putting a ARRAY{}
after the type of the elements in the curly braces. For example, an array of integers would be written as ARRAY{INT}
.
There are two ways of defining arrays. An array literal can be defined using the ARRAY
function.
[ARRAY 1 2 3]
An empty array can be initialized using the MAKE
function.
[MAKE ARRAY{INT}]
Arrays are pointers. This means that a change to an array, like appending to it, appends to all of its uses. For example:
[DEFINE a [ARRAY 1 2 3]]
[DEFINE b [VAR a]]
[APPEND [VAR a] 4]
[SET [VAR a] 0 0]
In this case, b
will also have 4
as the last element, and 0
as the first element.
Maps represent key-value data. For example, take the following table.
Key | Value |
---|---|
Name | Joe |
Favorite Color | Blue |
Favorite Language | B# |
Map types are defined by putting MAP{
, the key type, a ,
, the value type, and then a }
. For example, MAP{STRING,INT}
would be the type for a map with a key type of string and a value type of INT
. Maps can also be nested, for example MAP{STRING,MAP{STRING,INT}}
would be a map of a map.
To initialize a map use the MAKE
function. The [SET]
and [GET]
functions can be used to read and write to the map. The [EXISTS]
function returns a boolean of whether a key exists.
[DEFINE a [MAKE MAP{STRING,STRING}]]
[SET [VAR a] "Name" "Joe"]
[SET [VAR a] "Favorite Color" "Blue"]
[SET [VAR a] "Favorite Language" "B#"]
[PRINT [GET [VAR a] "Favorite Language"]]
Structs are like maps, but they only have a fixed number of keys. One advantage however, is that they can store values of multiple types.
In addition, structs are generally safer because a field is guarenteed to exist, and errors related to accessing nonexistent fields can be caught at compile time.
The above example could be remade to use structs as follows:
[DEFINE a [MAKE STRUCT{name:STRING,favoriteColor:STRING,favoriteLanguage:STRING,age:INT}]]
[SET [VAR a] name "Joe"]
[SET [VAR a] favoriteColor "Blue"]
[SET [VAR a] favoriteLanguage "B#"]
[SET [VAR a] age -1]
[PRINT [GET [VAR a] favoriteLanguage]]
Types can be given name using the TYPEDEF
declaration. For example:
[TYPEDEF a INT]
[TYPEDEF arr ARRAY{a}]
[DEFINE val [MAKE arr]]
INT
s, FLOAT
s, and STRING
s can all be converted to each other using type conversions. Use the INT
tag to convert a float or string to an integer, the FLOAT
tag to convert the other two to a float, and the STRING
tag to convert an integer or float to a string.
The ANY
type allows for passing values of multiple types around. It can be checked to be of a certain type using the CANCAST
tag, and cast using the CAST
tag. The ANY
tag can be used to cast a value to type ANY
. For example, to add 2 integers or floats you could do:
[FUNC ADD [PARAM a ANY] [PARAM b ANY] [RETURNS ANY]
[IF [CANCAST [VAR a] INT]
[DEFINE aval [CAST [VAR a] INT]]
[DEFINE bval [CAST [VAR b] INT]]
[RETURN [ANY [MATH [VAR aval] + [VAR bval]]]]
]
[IF [CANCAST [VAR a] FLOAT]
[DEFINE aval [CAST [VAR a] FLOAT]]
[DEFINE bval [CAST [VAR b] FLOAT]]
[RETURN [ANY [MATH [VAR aval] + [VAR bval]]]]
]
[RETURN 0]
]
[DEFINE vala [ANY 1]]
[DEFINE valb [ANY 2]]
[DEFINE res [ADD [VAR vala] [VAR valb]]]
[PRINT [STRING [CAST [VAR res] INT]]]
NOTE: This will cause a crash in the program if the values are anything other than two FLOAT
s or two INT
s!
If statements execute code conditionally. For example:
[IF [COMPARE 7 == 3]
[PRINT "7 is equal to 3."] # This executes when the condition is true
ELSE
[PRINT "7 is not equal to 3."] # This executes otherwise.
]
An if statement can also be done without the ELSE
.
[IF [COMPARE 7 > 3]
[PRINT "7 is greater than 3."]
]
While statements repeat until a condition is false. For example, to print the numbers from 1 to 10, a while statement could be used:
[DEFINE i 1]
[WHILE [COMPARE [VAR i] <= 10]
[PRINT [STRING [VAR i]]]
[DEFINE i [MATH [VAR i + 1]]]
]
A switch-case can be used instead of chaining if statements, when a value wants to be tested. For example, the following could be done with if statements:
[DEFINE val "Hello"]
[IF [COMPARE [VAR val] == "Foo"]
[PRINT "The value is foo."]
ELSE
[IF [COMPARE [VAR val] == "Bar"]
[PRINT "The value is bar."]
ELSE
[IF [COMPARE [VAR val] == "Hello"]
[PRINT "The value is hello!"]
ELSE
[PRINT "What is the value?"]
]
]
]
However, this is messy and inneficient. Instead, it could be done with a switch statement:
[DEFINE val "Hello"]
[SWITCH [VAR val]
[CASE "Foo"
[PRINT "The value is foo."]
]
[CASE "Bar"
[PRINT "The value is bar."]
]
[CASE "Hello"
[PRINT "The value is hello."]
]
[DEFAULT
[PRINT "What is the value?"]
]
]
These both achieve the same result, however the second is much easier to read and is more efficient.
NOTE: The DEFAULT
statement is optional.
Variables are defined and set using the DEFINE
tag.
[DEFINE a 1]
Would set a
to 1.
The value of a variable is accessed using the VAR
tag.
[DEFINE hello "Hello!"]
[PRINT [VAR hello]]
Would print the variable hello
, after defining it to "Hello!"
.
Variables can only store values of one type. In the previous example, the variable hello
would have a type of STRING
. Due to this, the code below is invalid.
[DEFINE hello "Hello!"]
[DEFINE hello 1]
Variables cannot be accessed outside the scope they are defined in. This means the code below is invalid code, since the variable a
cannot be accessed outside of the IF
.
[IF [COMPARE 1 = 1]
[DEFINE a "a"]
]
[PRINT [VAR a]]
Variables can be re-declared with different types if a new scope is declared. That is why the code below is valid code.
[DEFINE hello "Hello!"]
[IF [COMPARE 1 = 1]
[DEFINE hello 1]
[PRINT [STRING [VAR hello]]]
]
[PRINT [VAR hello]]
Use the MATH
tag to perform math operations.
[MATH 1 + 1]
Would return 2
.
The supported operators are listed below.
Operator | Effect |
---|---|
+ |
Addition |
- |
Subtraction |
/ |
Division |
* |
Multiplication |
^ |
Exponentiation |
% |
Modulo |
Use the CONCAT
tag to concatenate strings.
[PRINT [CONCAT "Hello, " "World!"]]
Functions are defined with the FUNC
tag. Provide paremeters with the PARAM
tag, and optionally, specify a return type with the RETURNS
tag. Return values with the RETURN
tag.
[FUNC ADD [PARAM a INT] [PARAM b INT] [RETURNS INT]
[DEFINE result [MATH [VAR a] + [VAR b]]]
[RETURN [VAR result]]
]
Above is an example of a function that takes and returns values. However, functions don't need to do this. The following is also valid.
[FUNC PRINTHELLO
[PRINT "Hello"]
]
An index of an array or character of a string can be gotten using the INDEX
tag.
For example
[INDEX [ARRAY 1 2 3] 0]
Would return 1
, the first index of [1, 2, 3]
. This can also be used to get characters of strings.
Arrays and strings can be sliced using the SLICE
tag.
[SLICE "abcde" 0 3]
Would return abc
. Since arrays are pointers, SLICE
doesn't return an array. Instead, use
[DEFINE arr [ARRAY 1 2 3 4 5]]
[SLICE [VAR arr] 0 3]
Now, arr
would be [1, 2, 3]
.
Use the IMPORT
function to import other B# files. This adds all functions in that file to the global scope and will run the code in that file. It will also add all global variables in that file to the current scope.
The files that are available to import are implementation-specific, except for the standard library files.
Import with
[IMPORT "math.bsp"]
Functions:
Usage | Description |
---|---|
[FLOOR val] |
Returns the floor of a float as an integer |
[CEIL val] |
Returns the ceil of a float as an integer |
[ROUND val] |
Rounds a float to an integer |
[RANDINT lower upper] |
Generates a pseudorandom number between two integers |
[RANDOM lower upper] |
Generates a pseuodorandom number between two floats |
Import with
[IMPORT "strings.bsp"]
Functions:
Usage | Description |
---|---|
[JOIN vals joiner] |
Joins vals (an array of strings) with the joiner in between |
[SPLIT str sep] |
Splits the string into an array of strings using sep as a seperator |
[TOUPPER str] |
Returns an all-uppercase version of str |
[TOLOWER str] |
Returns an all-lowercase version of str |
Import with
[IMPORT "json.bsp"]
Types Supported:
ARRAY{ANY}
MAP{STRING, ANY}
STRING
INT
FLOAT
Example:
[IMPORT "json.bsp"]
[DEFINE a [MAKE MAP{STRING, ANY}]]
[SET [VAR a] "a" [ANY 10]]
[SET [VAR a] "b" [ANY "hi"]]
[DEFINE c [MAKE MAP{STRING, ANY}]]
[SET [VAR c] "hi" [ANY "hello"]]
[SET [VAR a] "c" [ANY [VAR c]]]
[SET [VAR a] "d" [ANY [ARRAY [ANY 1] [ANY "2"] [ANY 3]]]]
[SET [VAR a] "e" [ANY 7.49]]
[PRINT [ENCODE [ANY [VAR a]]]]
Output:
{"e":7.49,"a":10,"b":"hi","c":{"hi":"hello"},"d":[1,"2",3]}
Name | Description |
---|---|
[TIME mode] |
Returns the current unix timestamp. NOTE: The mode is optional. If provided, it must be SECONDS , MICRO , MILLI , or NANO |