Skip to content

parkertomatoes/basbolt

Repository files navigation

BasBolt

Overview

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 inline errors screenshot

It also marks errors in the source code: inline errors screenshot

Live Demo - for a bonus, click a keyword and press F1!

How It Works

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:

  1. The editor transfers the source code to the server
  2. The server saves the source code to a file
  3. The server invokes BC.EXE (Microsoft BASIC Compiler) to compile
  4. The server transfers the listing file output to the editor
  5. The editor parses the listing file and annotates the source code

Transferring Files

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: compilation flow

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.

Compiling

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.

Building

1. Add BIOS Images

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

2. Build the compilation server

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.

3. Building

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.

Roadmap

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

Contributing

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.