BasBolt is an in-browser compiler explorer for QuickBASIC. It automatically compiles code into 16-bit assembly using Microsoft BASIC Compiler as you type, and integrates the results into the editor.
It shows generated assembly, colored to identify source code regions
It also marks errors in the source code:
Live Demo - for a bonus, click a keyword and press F1!
The editor keeps a V86 emulator instance in the background, running a small server on FreeDOS to facilitate communication. The server written in QuickBASIC (of course).
Whenever the code in the editor changes:
- The editor transfers the source code to the server
- The server saves the source code to a file
- The server invokes BC.EXE (Microsoft BASIC Compiler) to compile
- The server transfers the listing file output to the editor
- The editor parses the listing file and annotates the source code
V86 doesn't have an API to access FAT disks, but file transfer is made possible by reading and writing to the emulated system's RAM:
Since 16-bit real mode DOS doesn't concern itself about any silly nonsense like virtual memory protection, The server can just allocate a buffer, do a little arithmetic with the segment and offset, and write the physical address to the console:
CONST buffersize& = 16384
DIM SHARED buffer AS STRING * 16384
segment& = VARSEG(buffer)
IF segment& < 0 THEN segment& = 1 - segment&
pointer& = VARPTR(buffer)
IF pointer& < 0 THEN pointer& = 1 - pointer&
bufferaddr& = segment& * 16 pointer&
PRINT USING "(buffer&, size&) "; STR$(bufferaddr&); STR$(buffersize&);
Which prints something like this, redirected to COM1:
(buffer 123456, size: 16384)
The compilation server code can be found in another repostiory
The editor reads the pointer using the V86's serial API. V86 also has a convenient API to read and write blocks of memory in the emulated system:
const bytes = emulator.read_memory(address, size);
emulator.write_memory(bytes, address);
Since the RAM is implemented as a Uint8Array
, and read_memory
is implemented with UInt8Array.prototype.subarray
, we can effectively access shared memory. 16KB blocks are written at one time. A byte is sent over COM1 to signal that the data is ready to copied, and a message is sent back over COM1 to signal that the server is ready for more data. In formal CS literature, this transaction is called a podunk direct memory access (PDMA) transfer.
To compile, the server invokes the SHELL
command to run BC.EXE. Passing with the /A option generates a .lst file that includes each line of source, followed by the assembly for that line and any errors.
BC.EXE /A /O JOB.BAS JOB.OBJ JOB.LST
job.bas:
PRIN "HELLO WORLD"
job.lst:
PAGE 1
12 Dec 20
16:34:29
Offset Data Source Line Microsoft (R) BASIC Compiler Version 7.10
0030 0006 PRIN "HELLO WORLD"
^� Equal sign missing
0030 ** I00002: call B$CENP
0035 0006
46074 Bytes Available
45976 Bytes Free
0 Warning Error(s)
1 Severe Error(s)
yes, it's paginated and formatted to 80 characters for your dot matrix printer
Errors and assembly don't include any explicit line numbers and columns. But by counting lines of code, and spaces between the start of the line and the "^" for errors, the line and column of errors can be determined. The listing file is parsed using a combination of regular expressions and a simple state machine.
The assembly mappings in the listing files are not as fine-grained as the ones generated by modern compilers. It appears to only associate entire blocks of source code between branches and labels, with blocks of assembly. But it's still enough to give you a near rough idea of what source code becomes what assembly.
For X86 emulation with V86, a BIOS image is required to function:
- SeaBIOS is used as the bios, and should be placed in
images/seabios.bin
- Bochs VGABios is used as the VGA bios, and should be placed in
images/vgabios.bin
A hard disk image containing the compilation server must be built. It can be found here: https://github.com/parkertomatoes/basbolt-image
Run the makefile in that repository to build basbolt.img
, then copy it to images/basbolt.img
.
The project is packaged with npm and webpack, and can be built using the following commands:
npm install
npm run buildRelease
If the bundling is successful, the contents of /dist
can be deployed to any webserver.
This project is currently a fun tech demo that started as a joke, and was written as a way for me to practice writing a semi-complex project using React Hooks.
But it would be neat to make something like a tweakable sandbox for showcasing old Q(uick)?BASIC games and demos. Eventual features could include
- Linking
- Running
- Multiple sources
- Non-source files
- Multiple compilers (QB 1-4, PDS 7.1, VBDOS 1.0, etc)
- In-app help, etc
Are you of sound mind? And want to contribute? Welcome to that very narrow middle of that Venn diagram, friend. I'll happily review any issues or pull requests.