SPARCs Fly

From Esolang
Jump to navigation Jump to search

SPARCs Fly is an esolang by User:BoundedBeans, made to fulfill the following design decisions:

  1. Every single statement should be a block
  2. The only usable, modifiable data the program should have access to are virtual machines
  3. Programs should be extremely nested in structure
  4. SPARCs Fly should be an automatic system controller, specifically for QEMU virtualized sparc64 OpenBSD 7.5.

Because of design decisions #1 and #3, implementations should not have a recursion limit on parsing; this means that when parsing the program, they should not use up a new stack frame for each level of nesting.

Important Variations

The original specification of this programming language said that all VMs should be OpenBSD 7.5 SPARC64. Due to a bug that makes booting from a virtual hard disk on an OpenBSD 7.5 system for SPARC64 not work on QEMU (thus rendering that installer only), the language has been split into a few variations:

  • "SPARCs Fly" - OpenBSD 5.2 SPARC64 - The new standard, running on an old version because that fixes the bug in a way compatible with this language (some other options only work with -nographic text mode, which won't work with vmscreencapture).
  • "SPARCs Fly Legacyanide" - OpenBSD 5.2 SPARC (32-bit, only capable of 256MB of RAM) - A test that was alluded to in an old iteration of the SPARCs Fly Esolangs Wiki Article, and therefore exists to avoid disappointing people who wanted to suffer.
  • "AMD64s Fly" - OpenBSD 7.5 AMD64 - A wimp mode. An implementation should always scold the user/programmer upon running a program compiled/interpreted with this version.
  • "SPARCs Fly!" - OpenBSD 7.5 SPARC64 - The original specification, rendered installer only, and kind of a hardmode. An implementation should congratulate the programmer/user for their work upon running a program compiled/interpreted with this version. The name is only different from the standard version by a single terminating exclamation point.

Syntax

The program is a series of words separated by sequences containing space, horizontal tab, vertical tab, line feed, carriage return, and form feed characters.

Typically, the structure of the sequence of words is a series of smaller sequences of the pattern

block-name args* { inner-word* };

For example:

example 8 4 3 { example 6 55 { }; example 57 80 { }; };

Some block types have a second block delimited by [ and ];.

Block types

halt

halt { code };

Runs the code, then halts.

int

int n { code };

Adds an immutable integer with value n to the end of the storage, runs the code, then deletes the integer from the storage.

createvm

createvm vm-index-index { code };

Adds a vm to the end of the storage (currently empty and with the cd image inserted, must be installed to be used), runs the code, removes the vm from the storage. The vm-index-index is an index to an integer in storage (created by int), which is an identifier for the virtual machine, in order to differentiate between them if there are multiple.

ifvmexists

ifvmexists vm-index-index { code1 }; [ code2 ];

If the vm identified by the integer located at vm-index-index in the storage exists, run code1, otherwise run code2.

ifvmhascdimage

ifvmhascdimage vm-index-index { code1 }; [ code2 ];

If the vm identified by the integer located at vm-index-index in the storage still has the cd image inserted, run code1, otherwise run code2. If the vm does not exist, halt the program.

vmsendkey

vmsendkey vm-index-index keycode-index { code1 }; [ code2 ];

Sends a key message to the vm indicated by the integer at vm-index-index in the storage. The exact key sent is indicated by the keycode's respective key according to scan code set 1 of a US QWERTY PS/2 keyboard (here is a good resource for those codes). If the keycode is 0xE0, the next integer in the storage will be used as the extra byte for the extended keycode.

Runs code1 if successful, code2 if it failed in some way.

vmmovemouse

vmmovemouse vm-index-index x-index y-index { code1 }; [ code2 ];

Moves the mouse of the vm indicated by the integer at vm-index-index in the storage, to the position indicated by the integers as x-index and y-index in the storage.

If successful, run code1, otherwise run code2.

vmmousebuttons

vmmousebuttons vm-index-index val { code1 }; [ code2 ];

Changes the mouse button state of the vm indicated by the integer at vm-index-index in the storage. Val is a bitfield where 1=left, 2=middle, 4=right.

If successful, run code1, otherwise run code2.

vmscreencapture

vmscreencapture vm-index-index width height { code1 }; [ code1 ];

Captures the screen if the vm indicated by the integer at vm-index-index in the storage, but only if the width and height of the monitor match with the arguments. If they don't match, it is considered unsuccessful (meaning code2 is run). If successful, puts width*height*3 bytes as integers as the screen buffer, then runs code1, then removes all the new entries. Otherwise, doesn't do that and runs code2.

vmreboot

vmreboot vm-index-index remove-cd { code };

If remove-cd is not zero, removes the cd image so the vm can boot from the virtual hard disk. This is an irreversible change in this language; you cannot insert the cd-image back again except with a new vm. Reboots the vm indicated by the integer at vm-index-index in the storage, and runs code repeatedly until the system is ready to be interacted with, and runs code to completion if it's in the middle.

vmpause

vmpause vm-index-index { code };

Pauses the vm indicated by the integer at vm-index-index in the storage, and runs code when the vm is unpaused later.

vmunpause

vmunpause vm-index-index { code };

Unpauses the vm indicated by the integer at vm-index-index in the storage, and runs code if the VM is ever closed prematurely (by a crash or something). Yes, you have to pause and unpause to add a handler for that case. The handler gets a closure of the data in storage at the time the vmunpause was invoked, and will have the same data it once had there. Multiple handlers can be attached with multiple vmpause's and corresponded vmunpause's. The implementation should detect empty handlers and not add them for efficiency's sake.

vmsave

vmsave vm-index-index vm-snapshot-index-index { code1 }; [ code2 ];

Saves the state of the vm indicated by the integer at vm-index-index in the storage, to a snapshot identified by the integer at vm-snapshot-index-index in the storage.

Runs code1 if it works successfully, otherwise run code2.

vmload

vmload vm-index-index vm-snapshot-index-index { code1 }; [ code2 ];

Load the state of the vm indicated by the integer at vm-index-index in the storage, from a snapshot identified by the integer at vm-snapshot-index-index in the storage.

Runs code1 if it works successfully, otherwise run code2.

vmsocket

vmsocket vm-index-index new-vm-index-index { code };

Creates a new vm identified by the integer at new-vm-index-index in the storage, with a virtual network connection between it and the vm indicated by the integer at vm-index-index in the storage. Adds the new vm to the end of storage, runs code, removes the vm from storage.

deleteable

deleteable [true|false] { code };

Marks all vms created inside code to be deleted off of the physical disk when the SPARCs Fly program deletes them from storage if true, or to be persistent if false. This overrides any preference specified on a shallower nesting level. The default behavior is false.

Runs code.

ifequal

ifequal index1 index2 { code1 }; [ code2 ];

If the integers at the two locations in storage are equal, run code1, otherwise run code2.

ifgreater

ifgreater index1 index2 { code1 }; [ code2 ];

If the integer at index1 in storage is greater than the one at index2, run code1, otherwise code2.

for

for index1 index2 step-index { code };

Runs code multiple times for a series of integers located in storage starting at index1, up to index2, increasing by step-index. Before running code, add the pointed number to storage, and after the code finishes, remove it and add the next one, etc. This is intended to iterate pixels from the vmscreencapture command.

inf

inf { code };

Loops code infinitely. This is the only form of indefinite looping, but it can still have full functionality because it can contain mutable data (virtual machines declared outside the inf block), conditional statements, and halting. Plus, repetitively typing out a full length sequence of code activated conditionally and ending with a halt is sort of equivalent to a conditional break (but with a lot of bloated data stuck there). Putting a large portion of the loop in a conditional block is equivalent to a continue if the block doesn't run.

out

out byte { code };

Output byte to stdout, run code when finished.

in

in { code1 }; [ code2 ];

Inputs a bit of data from stdin, if the bit is 1, run code1, otherwise run code2.


Storage

SPARCs Fly has an unbounded, resizeable list of data elements. However, new data elements may only be appended, and only stick around for the length of a block, thus making data writing act like a stack (while reads are random access). This is completely inadequate for any form of storage on its own, however the virtual machines can have their ram and disk modified through the actions triggered by keyboard and mouse input, and read by interpreting the data from the screen.

A data element can be accessed by either a positive decimal number (from the beginning of the list), or a negative number (from the end of the list/top of the stack).

The same point in the program will always have the same number and type ordering of entries, no matter what; this is a deliberate design decision.

Virtual machines

Every virtual machine created by SPARCs Fly starts off with a cd image to install the specific OpenBSD version and platform with a virtual hard disk attached. OpenBSD was chosen along with SPARC for minimum compatibility with regular software while still having just enough to get by. The program that originally created it "owns" the hard disk and virtual machine, and is the only SPARCs Fly program able to access it. The program will have to go through installation itself (I was going to distribute a pre-installed hard disk image (if I ever actually make a SPARCs Fly reference implementation), but due to some weird US laws I cannot "export" OpenBSD to the internet myself because it contains cryptographic software), though once it is done with installation, it can detach the cd image and start booting from the virtual hard disk.

A SPARCs Fly program may send keyboard and mouse input to, process data from a screen capture of, pause, reboot, and take and restore snapshots of the virtual machine.

A SPARCs Fly program may have multiple concurrent virtual machines, even having some of them inactive to be reactivated later using snapshots.

VMs in storage that stop functioning while the program in running will become empty handles that throw an error when you try to do anything with them.

VMs are always referred to by an integer identifier. While they can appear in the storage inside the block of a vmcreate or vmsocket statement, they don't really do a whole lot in that form. These are remnants of old mental drafts where VMs were referred to by their index in the storage, rather than by an index to an integer identifier. It's confusing so I'm keeping it. This also means that if you're keeping persistent VMs across multiple executions, the program can just specify an already existing ID and the implementation should know what it's talking about. ifvmexists is your friend here.

Examples

Go ahead and try. You'll have to write a program that can go through an entire installer based on only the information available as colors on screen, then get it to run something meaningful. On the bright side, if you can get hexdump running, you can write any executable and thus prove SPARCs Fly Turing-complete!

However, there are some easy examples that don't require mutable storage and therefore don't require a vm:

Hello world!

out 72 { }; out 101 { }; out 108 { }; out 108 { }; out 32 { }; out 119 { }; out 114 { }; out 108 { }; out 100 { }; out 33 { };

Truth-Machine

Only checks the last bit of the digit, therefore @ and A also work as 0 and 1 for example.

in { }; [ ]; in { }; [ ]; in { }; [ ]; in { }; [ ]; in { }; [ ]; in { }; [ ]; in { }; [ ]; in { inf { out 49 { }; }; }; [ out 48 { }; ];