-
Notifications
You must be signed in to change notification settings - Fork 4
Starter Guide
last updated 29/12/2022
Welcome to the B* Starter Guide! B* is a programming language that is used in TBOTC's tag system.
If you already know B++, check out the changelog, and Chapters 3, 5, 7, 9 and 10!
Want to know how B* does things compared to Javascript? Check out Zelo101's JS2B* converter!
To run B* code, type tc/bstar run following your code. Your code can either be a message or a text file!
To run a B* tag, you can use tc/bstar [name] (optional arguments). A tag is a user-made command that uses B*.
Hello world is actually very simple, all you have to do is type:
Hello, World!like B++, B* effectively echos any strings given to it! Unforunately, this also means the example does not teach you anything about B*! So, lets create a more advanced "Hello, World!" using functions.
Think of a function as a machine that takes an input, changes said input then outputs the manipulated input. In B*, everything square block is a function.
All functions require inputs called parameters. For example, [REPEAT] has the parameters string & amount. Some functions have optional parameters, and some even have infinite parameters ([CONCAT] for example!)
Functions can be nested, to create more complex programs. This is a fundamental concept in functional programming. In the computer science realm, B* is a Lazy language.
Here is an example of a nested function:
[REPEAT [CONCAT "Hello, " "World!" " "] 10]This would first concatenate the three strings "Hello, ", "World!" and " " to "Hello, World! "
🤔 Notice how I've added a space to the end of the concatenated string. This is so that there will be a space between every repeated "Hello, World!".
The string "Hello, World! " will then be repeated 10 times to give the output:
Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World!🤔 Notice how the space at the end was cut off. B* automatically trims any spaces at the start or end of the output. This is only done at the end of the program, not during.
The type of data you input to a function can change the way the function works.
| Type | Example |
|---|---|
| string | "Hello World!" |
| integer | 63467575 |
| float | 4.1274 |
| array | {1, 2, 3, 4, 5} |
| function | [REPEAT "Hello!" 10] |
Some functions will always output to a specific type, and this will be addressed in the documentation. For example, [DIV] will always output a float.
There are functions for each of these types that allow you to convert data to that type. Arrays start from 0.
🤔 If you see a type in the documentation that is just number, that means you can use a mix of integers or floats!
After a tag runs, all variables created will be deleted. But what if you wanted a variable to stay? This is where global variables come in. These variables will stay even after running, and can be read by other tags!
To define a global variable, use [GLOBAL DEFINE]. To read a global variable, use [GLOBAL VAR] They both act the same as it's regular counterparts.
Everyone's tags can read all global variables, but only the owners tags can edit them.
You can create your own functions using the [FUNCTION] function...
Let's say we want to create a function that gets the square root of a number.
We can do this:
[FUNCTION root {"number"} [POW [VAR number] 0.5]]root is our function name and it's how we can call it.
{"number"} is our arguments which are contained in an array. We can use number with [VAR number].
[POW [VAR number] 0.5]] is the body of the function. You can have multiple functions inside the body, but the last function will be the value the function returns.
Arguments let users give inputs for your tag! Ok so, I want to make a tag called e that repeats 'e' an X number of times based on input by the user
To do this, we can use the [ARGS] function. This will give an array of the user's inputs, and if we just want a certain argument, we can also do something like [ARGS 0].
The tag code is similar to Chapter 2, except this time we can replace 10 with our [ARGS] function!
[REPEAT "e" [ARGS 0]]But what happens if somebody forgets to add an input? Lets add a check that tells the user if they do this. We can check by wrapping the above code in an [IF] function.
[IF [COMPARE [LEN [ARGS]] > 0]
[REPEAT "e" [ARGS 0]]
"This tag requires an input for length!"If an argument exists, we can continue knowing that the code wont error. But if not, we can politely tell the user to add one!
But we haven't solved all the edge cases. What if the user gives us a string of text? Try to figure out how to solve this!
Lets say there is a tag called root, which has the code in the previous Chapter 5. How could I access that function without re-inventing the wheel?
This is where [IMPORT] comes in. It can import any [FUNCTION] from a tag. To import root, we just have to do[IMPORT root]
at the start of our code, and now we can freely use it just like any other function!
Be aware, importing a tag will import every function!
Here are some helpful commands on publishing your tag!
| Command | Description |
|---|---|
| tc/bstar run [code (or a file)] | Runs B* code. |
| tc/bstar create [name] [code (or a file)] | Creates a tag. |
| tc/bstar edit [name] [code (or a file)] | Edits a tag you've made. |
| tc/bstar info | Gives a list of most-used tags! |
| tc/bstar info [name] | Gives statistics on a tag, including its code. |
| tc/bstar delete | Deletes a tag you've made. There is no confirmation! |
B*'s functions are designed to eventually collapse into a single simple type (This is what running a tag does!). Because of this, some functions act differently compared to counterparts in different languages.
All function parameters can also be functions, as long as they return the correct type. Because of this you can do things such as [VAR [CHOOSE "foo" "bar"]] which will randomly choose between [VAR "foo"] and [VAR "bar"]. And yes, this extends to even randomly choosing functions!
Once a LOOP, FOR, and WHILE loop is complete, it will return an array, with each iteration being stored.
B* has no scope, so assume that all variables declared have a "global" scope.
B*'s design can make it tough to debug when something goes horribly wrong. Forunately, and... hopefully... TBOTC should give you a reasonable error message telling you what happened, and where it occured. There are two types of error messages.
Example of a runtime error:
(random error message joke here)
Error of type TypeError at
function
block REPEAT
string "e"
function
block ARGS
integer 0
**can't multiply sequence by non-int of type 'str'** (Error occurred at code block 1)
This shows you the full tree of the perpetrator, along with its types. That way you can focus your efforts on the single function.
Example of a parser error:
Unexpected end-of-input.
Expected one of:
* RSQB
* SIGNED_INT
* FALSE
* ALPHANUMERIC
* LBRACE
* LSQB
* TRUE
* LSQB
* ANON_0
* DECIMAL2
* LBRACE
* _WS
* RSQB
* LSQB
* LBRACE
* ESCAPED_STRING
* ANON_1
* _WS
* LSQBThis means that the B* parser could not parse your code for whatever reason, though this is almost always due to a missing bracket or quotes.
Numbers n' Vars || Arrays || Strings || Control flow || Meta || # || How do I start?