GRUBBY

GRUBBY is a name given to an underutilized graphical "programming language" by User:BoundedBeans.
==Description==
To run a GRUBBY program, start up some sort of x86 virtual machine booting off of some sort of medium or disk containing only the GRUB2 bootloader and a single grub.cfg file containing the text of the program. Note that since the initial GRUB must be on the EFI partition, which uses a filesystem similar to FAT32, there is a limit of around 4 gigabytes of program text, since that is the maximum size of a file in FAT32. This limit does not necessarily prevent GRUBBY from being Turing-complete, since a Turing-complete system may be constructable within 4 gigabytes using disk or RAM storage (disk space and RAM size is not usually considered to affect computational class as it is a technical limitation that does not affect the language).
The full language specification is spread across the Official GNU GRUB2 Manual. 
==Tricks for computation==
The first major limitation is that there is no numerical arithmetic. This is tricky and slow to implement, but is just barely possible with the features GRUB2 configuration files provide.
===Data storage===
You can store in environment variables using the set commands.
===Conditionals and looping===
The first thing we need are conditionals and looping.
Luckily we have for, if, elif, else, while, similar to the Bourne shell commands of the same name. We also have until, which is redundant with while with an inverted condition but makes things slightly easier, and function, which is really good since we can organize the code.
$? is the exit status of the previous command, which is useful if we want to store it in a variable to check later, since otherwise commands would have to be directly in a conditional.
The loops, however, are constant length (using for with n words) until we can make a given length of number (such as strings of 16 or 32 binary digits).
===Lookup table===
We can create a binary lookup table for logic gates operating on strings containing one binary digit, using conditionals.
===Strings of digits===
We can create a full number with a list of bits, but to to make an adder or subtractor using the logic gate lookup tables, we need ways to concatenate strings and separate them.
Luckily, concatenation is easy. Just store both segments in variables s1 and s2, then store ${s1}${s2} in a variable. (You can change s1 and s2 to any valid identifier).
Separating strings is a lot harder. The only way the author could find is the regexp command. This command has an argument --set [name] to assign the match to a variable, but you can prefix the name with a number n and a colon to store the nth match of the regex in the string.
So, to get the first character of the variable string, use:
 regexp --set result '.' ${string}
Every other character except the first can be obtained with:
 regexp --set 2:result '^.|.*$' "${string}"
The result will be stored in the variable result.
This means numbers are optimally stored with the least significant bit first, in order to be added and subtracted.
===Indefinite loops===
Once we make numbers, we can now use them for looping. We can reverse a least-significant first binary string by taking the head over and over, replacing the string with its tail, and prepending the head to a new growing string. The reversed string will lexicographically order itself in the same way than numbers would order themselves, though negative numbers would complicate this and maybe require an additional conditional. 
Using the test command allows us to check string equality and lexicographical comparison with the ==, !=, <, >, <=, and >= operators. By using a fixed length number, we can have arbitrary counted for loops, as well as richer while and until loops.
===I/O===
Printing to the screen can be done with the echo command.
Input can be done with the read command, which has the option of storing into a variable.
===Drivers===
Drivers can be implemented with the undocumented in* and out* commands for reading and writing I/O ports. From here you can connect to the disk, keyboard, mouse, USB devices, network, etc. This is just enough to implement a full operating system in GRUBBY, along with the also undocumented read_* and write_* for RAM and memory-mapped devices.
Seemingly, through reading the source code (grub-core/commands/iorw.c and grub-core/commands/memrw.c), the formats are:
 in(TYPE) [-v VARNAME] PORT
 out(TYPE) PORT VALUE [MASK]
 read_(TYPE) [-v VARNAME] ADDR
 write_(TYPE) ADDR VALUE [MASK]
===Arbitrary assembly execution===
There appears to be no way to do this in RAM, since the program counter is a register, not a RAM value.
The only way to do this is:
# Write a disk driver for every disk that you plan on being compatible with, using the information in the #Drivers section.
# Use the appropriate driver to write the code onto the disk.
# Use the chainloader command to boot into another bootloader and maybe kernel on disk. Potentially an entire operating system, though the process of writing it and everything else would have to fit into 4 gigabytes, putting a harsh limit on the size of the operating system.
Unfortunately, the chainloader command can only be used from within a menuentry block. The good thing is that menuentry supports any commands inside of its block, so we can just write all the code inside of a single menuentry, then execute it automatically by setting the timeout variable outside of the menuentry block (setting it to 0 runs it instantly).
===Self-modification===
It's possible using drivers for a GRUB configuration file to modify its own file on disk. I'm not really sure how that would play out since it depends on whether GRUB reads it all at once (in which case it would require a restart), or sequentially (in which case self-modification could happen at runtime but would have weird effects).
GRUB configurations files are also capable of loading other configuration files of the same format using source FILE, and that configuration file could be rewritten and executed again for more nuanced eval. Potentially it could also load itself???, in which case self-modification may be somewhat simpler.
==Examples==
If the command line doesn't show up for these, try pressing c.
===Hello, world!===
 echo 'Hello, world!'
===Truth-machine===
 read truth
 if test truth == 0;
 then
 echo 0
 elif test truth == 1;
 while true;
 do
 echo 1
 done
 fi
===Logic gate calculator===
Input as (1|0) (AND|OR|XOR) (1|0).
 function AND {
 if test $1 == 0;
 then
 set result=0
 else
 set result=$2
 fi
 }
 
 function OR {
 if test $1 == 1;
 then
 set result=1
 else
 set result=$2
 fi
 }
 
 function XOR {
 if test $1 == $2;
 then
 set result=0
 else
 set result=1
 fi
 }
 
 read logic
 regexp --set gate 'AND|X?OR'
 regexp --set 1:left '[10]'
 regexp --set 2:right '[10]'
 eval ${gate} ${left} ${right}
 echo ${result}
===Truth-machine with a nice menu for selecting 1 or 0===
 menuentry "0/False" {
 echo 0
 }
 
 menuentry "1/True" {
 while true;
 do
 echo 1
 done
 }
 
Category:Languages
Category:1995 
Category:Unknown computational class
Category:Unimplemented