Babalang
Babalang is a thematic, Turing-complete esoteric programming language inspired by the rule system within the (also Turing-complete) indie game Baba Is You. The language is based around statements (akin to rules in Baba Is You), each with a subject, verb, one or more targets, and an optional condition. The language supports variables, loops, functions, structs, and basic IO operations. Babalang was written in March of 2020 by User:RocketRace, and is currently in version 1.1.1.
Syntax
A Babalang program (marked with a .baba
file extension) consists of a stream of statements. Each statement consists of words. The program will execute each statement successively until it reaches EOF, after which the program halts.
Words (and comments)
A word is a string of alphanumeric ASCII characters (plus underscores), surrounded by any amount of whitespace. Valid words include, but are not limited to: baba
, VALID_Word_123
, 0123456789
, and _____
. Note that the language is case-insensitive by default, meaning baba
and BABA
are parsed as the same word. Any other characters are interpreted as whitespace, with the exception of comments. A comment, or any text following //
until a line break, will be ignored by the parser.
A word may be either a keyword or an identifier. Keywords are reserved words associated with actions in the program. Identifiers represent names, such as variable, function or struct names. A word can be further divided into the following seven categories:
Word type | Valid words |
---|---|
Noun | All identifiers (and therefore all words not specified here) are, by default, nouns. The following keywords are nouns: all , empty , image , and level .
|
Verb | The following keywords are verbs: is , has , make , fear , follow , eat , and mimic .
|
Property | The following keywords are properties: you , win , defeat , move , fall , turn , more , right , up , left , down , chill , you2 , group , shift , sink , swap , text , word , done , tele , float , and power .
|
Condition | The following keywords are conditions: on , near , facing , and without .
|
Prefix | The following keywords are prefixes: lonely , idle , often , seldom .
|
Not | Only the literal not is in this category.
Note: where-ever |
And | Only the literal and is in this category.
|
These will all be detailed later on.
Statements
A statement is built of words in a specific pattern. Most statements are of the following pattern:
[noun] [verb] ([noun] | [property])
Examples of this pattern include BABA IS YOU
, SKULL IS EMPTY
and ALL IS DONE
.
While this pattern suffices for most use cases, the language supports more syntax.
The actual pattern used for validating statements is more complex:
[not]? [prefix]? [noun] ([not]? [condition] [noun] ([and] [noun])* [verb] [not]? ([noun] | [property]) ([and] [not]? ([noun] | [property]))* ([and] [verb] [not]? ([noun] | [property]))?
Here, [noun]
represents a noun, [verb]
a verb, and so on.
An abstract representation of this pattern is shown below:
[some_prefix]? [subject] [some_conditions]? [major_action] ([AND] [minor_action])?
Here is a rundown of each of these, in order of relevance:
subject |
Each statement must define a subject. A subject can be any noun. |
major_action |
Each statement must define a major action. This begins with a verb, followed by one or more targets separated by AND . If the verb is IS , targets may be nouns or properties. If the verb is any other word, targets must be nouns. Targets may be optionally negated with NOT . Multiple NOT words will cancel each other out. A NOT word will carry its effect to all the targets following it.
Examples of major actions include: |
some_conditions |
Statements can define optional conditions. If the conditions pass, the rest of the statement will be executed. This begins with a condition word, followed by one or more targets separated by AND . Each target must be a noun. The condition word may be prefixed by NOT , reverse the condition being checked for. (Note: unlike with major_action, each target in a condition may not be prefixed with NOT .)
Examples of valid conditions include: |
some_prefix |
Similar to above, a statement can define an optional prefix. If the prefix passes, the rest of the statement will be executed. A prefix is simply a prefix word, optionally prefixed with NOT to reverse its effects.
Examples of valid prefixes include: |
minor_action |
This is similar to major_action , but only allows one target. A minor action still occupies the same statement as a major action, meaning they will utilize the same conditionals.
Examples of valid minor actions include: |
So, considering these rules, here are some valid statements:
BABA IS YOU
BABA NEAR KEKE IS FALL AND NOT MOVE
NOT NOT NOT IDLE KEKE NOT ON KEKE HAS BABA AND BABA AND IS DEFEAT
BABA FACING KEKE AND ALL IS ALL AND NOT BABA AND KEKE AND EMPTY
LONELY BABA FEAR KEKE AND IS WIN
And equally, here are some invalid statements:
BABA AND KEKE IS YOU
(only one subject allowed)BABA HAS YOU
(properties can only be used inIS
statements)LONELY AND IDLE BABA IS YOU
(only one prefix allowed)BABA FACING NOT KEKE IS MOVE
(can't negate condition-targets)BABA IS
(incomplete statement)
Runtime
Since Babalang is an interpreted language, a majority of its grunt work is done at runtime. This includes its object creation, type system, scope management and pointers.
Objects
As a rule of thumb, most things in Babalang are objects. Functions, structs, user-defined variables, the null-like EMPTY
object... All of these are considered objects.
Objects in Babalang behave like objects in some other languages; Babalang objects are bound to variables and can be accessed like so, as well as having a type. A way to explicitly bind objects is below:
variable_name IS some_type
where some_type
is a type (see below).
There are many ways to implicitly define / bind a variable:
An object may be copied using the following syntax:
variable_name IS other_variable_name
This will bind the value of other_variable_name
to variable_name
, implicitly defining variable_name
.
Types
There are six builtin types and two types, designed to emulate custom types. While objects are typed, the typing is dynamic (when implicitly defined). Objects may also have their object type casted to a different type, or even shadowed with a new object, by functions and methods.
Methods can be called on objects of the appropriate type.
YOU
YOU
is a primitive type, equivalent to an unsigned 8-bit integer 2-tuple. An object can be explicitly declared as YOU
like so:
variable_name IS YOU
A YOU
object contains the following information:
x
, a wrapping unsigned 8-bit integer,y
, a wrapping unsigned 8-bit integer, anddir
, a wrapping unsigned 2-bit integer (representing the 4 cardinal directions, starting from RIGHT and going counterclockwise).
(These fields are not accessible directly.)
The following methods can be called on YOU
objects:
Method | Syntax | Behavior |
---|---|---|
MOVE |
[YOU] IS NOT? MOVE |
Moves the X or Y position of the object one unit in the direction it is facing. Note that this is wrapping, and that an object facing UP (dir=1) with a Y position of 255 (max value) will wrap back to Y=0.
When called with |
FALL |
[YOU] IS NOT? FALL |
Resets the X or Y position of the object, depending on the direction the object is facing.
If facing left or right, resets the X position. Otherwise, resets the Y position. If called on its own, sets the value of the axis to 0. If instead called with |
TURN |
[YOU] IS NOT? TURN |
If called on its own, rotates the object clockwise. If instead called with NOT , rotates counterclockwise instead.
|
MORE |
[YOU] IS NOT? MORE |
Shifts the binary representation of the X or Y value of the object.
If facing left or right, shifts the X value. Otherwise, shifts the Y value. If called on its own, shifts the value left by one bit. If instead called with Any values shifted outside of the range of u8 will be discarded, i.e. a value of 1 shifted right once turns into 0. |
RIGHT |
[YOU] IS NOT? RIGHT |
Sets the direction of the object to 0 (right).
When called with |
UP |
[YOU] IS NOT? UP |
Sets the direction of the object to 1 (up).
When called with |
LEFT |
[YOU] IS NOT? LEFT |
Sets the direction of the object to 2 (left).
When called with |
DOWN |
[YOU] IS NOT? DOWN |
Sets the direction of the object to 3 (down).
When called with |
CHILL |
[YOU] IS NOT? CHILL |
Sets the X or Y value of the object to a random value between (and including) 0 and 255.
If facing left or right, randomizes the X value. Otherwise, randomizes the Y value. This has no behavior when called with New since version 1.1 |
TEXT |
[YOU] IS TEXT |
Prints the ASCII representation of the X or Y value of the object to STDOUT.
If facing left or right, prints the X value. Otherwise, prints the Y value. This has no behavior when called with |
WORD |
[YOU] IS WORD |
Copies one byte from STDIN into the X or Y value of the object.
If facing left or right, copies into the X value. Otherwise, copies into the Y value. This has no behavior when called with |
WIN |
[YOU] IS WIN |
Exits the program with return code 0.
This has no behavior when called with |
DEFEAT |
[YOU] IS DEFEAT |
Exits the program with return code 1.
This has no behavior when called with |
SLEEP |
[YOU] IS SLEEP |
Sleeps for seconds equal to the X or Y value of the object.
If facing left or right, sleeps for X seconds. Otherwise, sleeps for Y seconds. This has no behavior when called with New since version 1.1 |
YOU
objects also define the following special syntax:
[YOU] IS NOT? [YOU] AND NOT? [YOU] AND [...]
This will go through each object in the right-hand-side of the statement, take the sum of each object's X and Y positions (modulo 256) and set the subject's X and Y values to the result. Anything prefixed with NOT
will be subtracted from the final sum instead of being added to it. The keyword ALL
may also be used instead of a YOU
-type object to replace any object: ALL
here represents the sum of all YOU
-objects' values in the current scope.
For instance, the following will compute A = B + C - D
:
A IS B AND C AND NOT D
And the following will compute A = [sum of every YOU object] - C
:
A IS ALL AND NOT C
Edge case: ALL
Every method of a YOU
object can also be applied to the keyword ALL
. When this is done, the method will be applied to every YOU
object in the current scope simultaneously, such that:
BABA IS YOU KEKE IS YOU BABA IS MOVE AND TURN KEKE IS MOVE AND TURN
is equivalent to:
BABA IS YOU KEKE IS YOU ALL IS MOVE AND TURN
YOU2
New since version 1.1
YOU2
is equivalent to YOU
, except its maximum value is 2^16 - 1 (65535) rather than 2^8 - 1 (255). In general, whenever YOU
deals with 255, YOU2
deals with 65535. YOU2
objects may be used whereever YOU
objects are valid. Here are all the other differences between YOU2
and YOU
:
Instruction | Change |
---|---|
SLEEP |
Instead of sleeping for seconds equivalent to the x or y value, sleeps for milliseconds instead. This allows for finer control over timing. |
TEXT |
If the top 8 bits of the object's active axis value (x / y) are 0, prints the ASCII equivalent of the bottom 8 bits only. Otherwise, prints the ASCII representation of the top 8 bits, then the bottom 8 bits. |
GROUP
GROUP
is a primitive type, equivalent to an unbounded stack. A GROUP
object can contain any objects, including other GROUP
objects. An object can be explicitly declared as GROUP
like so:
variable_name IS GROUP
A GROUP
object, in addition to functioning as a stack, has an index attribute which can be manipulated (see below) to effectively grant array access into the object.
GROUP
objects define the following syntax:
[GROUP] HAS variable_name AND variable_name AND [...]
This will push elements (the [OBJECT]
s) to the GROUP
, in sequential order.
GROUP
objects also define the following syntax:
[GROUP] MAKE variable_name
This will pop the last element of the GROUP
and bind that object to the variable on the right side. If the variable is already bound, this will shadow the variable!
The following methods can be called on GROUP
objects:
Method | Syntax | Behavior |
---|---|---|
SHIFT |
[GROUP] IS NOT? SHIFT |
Increments or decrements the index attribute of the object. If the index goes out of bounds of the stack, it will wrap around.
If called on its own, increments the index. If instead called with |
SINK |
[GROUP] IS SINK |
Discards the last element of the stack.
This has no behavior when called with |
SWAP |
[GROUP] IS SWAP |
Swaps the last element of the stack with the element at the position of its index attribute. (Swaps the last element and the i-th element)
This has no behavior when called with |
TURN |
[GROUP] IS TURN |
Reverses the contents of the GROUP object in place.
This has the exact same behavior when called with New since version 1.1 |
TEXT |
[GROUP] IS TEXT |
Calls IS TEXT on every element of the GROUP .
This has no behavior when called with |
WORD |
[GROUP] IS WORD |
Reads one line of input from STDIN, creates YOU objects with their X value set to each byte of the input, and finally pushes each object to the GROUP .
For example, reading This has no behavior when called with |
EMPTY
This is the nil object. It is always accessible through the EMPTY
noun keyword.
All methods called on EMPTY
will throw an error, except TEXT
, which does nothing at all.
LEVEL
LEVEL
objects are functions. They take in arguments, execute a callback when called, and return values.
A LEVEL
object is defined using the following syntax:
variable_name IS LEVEL // Any amount of statements of the form `variable_name HAS argument_name AND argument_name AND [...]` // Any amount of statements variable_name IS DONE
(Note that HAS argument_name
, or even IS DONE
, can be fulfilled using the minor_action behavior outlined previously.)
The following syntax is the equivalent of a return
statement in normal languages:
[LEVEL] MAKE variable_name
This is only valid inside function bodies, and will return the object on the right hand side from the function. LEVEL
objects will implicitly return the EMPTY
object.
Already-defined LEVEL
objects define the following syntax:
[LEVEL] HAS variable_name AND variable_name AND [...]
This pushes arguments to the LEVEL
object, like adding arguments to a normal-language function (fn(args)
).
The following method can be called on a defined LEVEL
object:
Method | Syntax | Behavior |
---|---|---|
POWER |
[LEVEL] IS POWER |
Calls the LEVEL object, binding each of its arguments to the parameter names in its definition.
This will throw an error if the amount of arguments supplied does not match the amount of parameters in the definition. The return value of the This cannot be called with |
IMAGE
IMAGE
objects are structs with a constructor. They hold mutable attributes which may be bound to any object.
There are two distinct types of IMAGE
s: IMAGE
definitions and IMAGE
instances.
IMAGE
definitions are defined with the following syntax:
variable_name IS IMAGE // Any amount of statements of the form `variable_name HAS attribute_name AND attribute_name AND [...]` // A constructor is mandatory variable_name IS LEVEL // The constructor must take AT LEAST one argument. This will be bound to `self`, like in normal object-oriented languages. variable_name HAS argument_name AND argument_name AND [...] // Any amount of statements variable_name IS DONE variable_name IS DONE
The constructor takes at least one argument. The first argument will be bound to the new instance of the IMAGE
, whenever created. The constructor will implicitly return the first argument at the end of its block. It's discouraged to explicitly return from the constructor, as that introduces unintuitive behavior to your program.
IMAGE
instances are instantiated the same way LEVEL
objects are called: They are initialized with:
variable_name IS [IMAGE_DEFINITION]
, after which arguments are provided and the whole thing is called with variable_name IS POWER
. This will bind the object returned from the constructor into variable_name
, which usually should be an IMAGE
instance.
IMAGE
definitions and instances have an attribute pointer, which is used to access and modify attributes of the IMAGE
definition or instance.
IMAGE
definitions and instances define the following syntax:
[IMAGE] FOLLOW attribute_name
This will set the attribute pointer to any valid name provided.
[IMAGE] MAKE variable_name
This will copy the attribute pointed to by the attribute pointer into variable_name
.
[IMAGE] EAT variable_name
This will copy the object bound to variable_name
into the attribute pointed to by the attribute pointer.
References
A reference to an object may be created by calling [OBJECT] MIMIC [OTHER_OBJECT]
. This will bind OBJECT
to a reference to OTHER_OBJECT
. Any operations performed on OBJECT
will be performed on OTHER_OBJECT
instead.
Scopes
Variables in Babalang are defined in their own scope.
The program scope is the outermost scope, which contains inner (potentially nested) scopes.
LEVEL
callbacks, as well as IMAGE
constructors are inner scopes.
By default, every variable is only accessible from the local scope, that is, they are not accessible from within an inner scope. So BABA
defined in the program scope is not accessible from within the callback of KEKE
, a LEVEL
.
The arguments for a LEVEL
are only bound in the scope of their callback. The same applies for the arguments for an IMAGE
constructor.
It is possible to make a variable FLOAT
, that is, it will be accessible (and mutable) from within any inner scopes. This is possible for explicit definitions, using the following syntax:
variable_name IS FLOAT variable_name IS TYPE // May be YOU, GROUP, LEVEL or IMAGE // Continue definition like normal
This has the effect of making the variable a static variable.
Additionally, it is possible to make the result of a function call global, by prepending a FLOAT
statement to a POWER
statement:
// variable_name is an instance of LEVEL or IMAGE (class) variable_name IS FLOAT variable_name IS POWER
Loops
A loop may be defined using the following syntax:
variable_name IS TELE // Any amount of statements to loop variable_name IS DONE
When executed, this will repeatedly execute the statements inside the loop. The following syntax may be used to break from a loop:
other_variable FEAR variable_name // where `variable_name` is the same as above
This will break from whatever loop variable_name
is bound to! This means that you can break through multiple layers of loops with a single statement.
FEAR
will unconditionally break from the loop. However, this is not very useful for most loops in which you want to break once a condition is met. This is where conditionals come in:
Conditionals
Any statement can be defined with conditionals. As seen under the Statements heading, there are four types of binary conditionals (ON, NEAR, FACING, WITHOUT
) and two types of unary conditionals (LONELY, IDLE
).
A statement with a conditional attached to it will only execute if that conditional returns true. A conditional is bound to the subject of a sentence. This means that BABA ON KEKE IS MOVE
will call BABA IS MOVE
iff the conditional BABA ON KEKE
returns true.
Here is a reference of each binary conditional:
Conditional | Syntax | Behavior |
---|---|---|
ON |
[OBJECT] NOT? ON [OTHER_OBJECT] AND [OTHER_OBJECT] AND [...] |
"Equals" conditional
If called with Returns true if the subject is equal to each of the conditional-targets. Throws an error if the objects are not of the same type! For For For For For For For references, returns true if the objects point to the same object. If the RHS is |
NEAR |
[OBJECT] NEAR [OTHER_OBJECT] AND [OTHER_OBJECT] AND [...] |
"Type-equals" conditional
If called with Returns true if each of the objects are of the same type. If the RHS is For |
FACING |
[OBJECT] FACING [OTHER_OBJECT] AND [OTHER_OBJECT] AND [...] |
"Less-than" conditional
If called with Returns true if [left_hand_side] < [right_hand_side]. Throws an error if the objects are not of the same type! For
For If the RHS is Throws an error for |
WITHOUT |
[OBJECT] WITHOUT [OTHER_OBJECT] AND [OTHER_OBJECT] AND [...] |
"Does Not Contain" conditional
If called with Returns false if [right_hand_side] is an element of [left_hand_side]. This throws an error if the LHS is not a (Note that this will copy the values of the RHS and check whether the If the RHS is |
Here is a reference of both unary conditionals:
Conditional | Syntax | Behavior |
---|---|---|
LONELY |
NOT? LONELY [OBJECT] |
"Null" conditional
If called with Returns true if the object is "null". For For For For For |
IDLE |
NOT? IDLE [OBJECT] |
"Callable" conditional
If called with Returns true if the object can be called right this instant (using For For For any other objects, returns false. |
OFTEN |
NOT? OFTEN [OBJECT] |
"Probably" conditional
If called with Returns true randomly with 0.75 probability. New since version 1.1 |
SELDOM |
NOT? SELDOM [OBJECT] |
"Probably not" conditional
If called with Returns true randomly with 0.1667 (1 in 6) probability. New since version 1.1 |
Errors
If the interpreter encounters a problem with your program, it will complain and throw an error.
Whenever an error is thrown, a short message is printed to the console with context to the error, and the program will halt.
Errors are not first-class.
Examples (commented)
Hello, world!
// Initialize constants // Even though these are numbers, the interpreter considers them variables. // This is important to keep in mind. 1 is you and move 2 is 1 and 1 4 is 2 and 2 8 is 4 and 4 16 is 8 and 8 32 is 16 and 16 64 is 32 and 32 // Initialize a stack hello_world is group // Initialize character with value "H" (decimal 72) H is 64 and 8 // Initialize the rest of the characters e is 64 and 32 and 4 and 1 l is 64 and 32 and 8 and 4 // `X and not Y` stands for `X + (-Y)` o is 64 and 32 and 16 and not 1 comma is 32 and 8 and 4 sp is 32 w is 64 and 32 and 16 and 8 and not 1 r is 64 and 32 and 16 and 2 d is 64 and 32 and 4 excl is 32 and 1 newline is 8 and 2 // Push the appropriate characters to the stack hello_world has H and e and l and l and o and comma and sp and w and o and r and l and d and excl and newline // Print the contents of the stack hello_world is text
Echo
// Simple program to echo user input // If the input is blank, halt the program baba is group keke is group flag is tele // Loop: baba is word // Take input keke is baba and sink // Strip newline lonely keke fear flag // Check for empty input baba is text // Print output flag is done
Fibonacci
See this gist.
99 bottles of beer
See this gist. Written by FLeckami#3853 (@691193184251805774) on Discord!
Truth machine
See the truth machine page for a program, written by FLeckami#3853 (@691193184251805774) on Discord.