Skip to content

Releases: ngeor/rusty-basic

v0.8.0

25 Sep 19:11
Compare
Choose a tag to compare
Releasing version 0.8.0

v0.7.0

11 Dec 07:37
Compare
Choose a tag to compare
Releasing version 0.7.0

v0.6.1

19 Oct 05:41
5b2ceb4
Compare
Choose a tag to compare
Releasing version 0.6.1

v0.6.0

08 May 18:50
Compare
Choose a tag to compare

πŸŽ‰πŸŽ‰πŸŽ‰ This is a significant milestone for rusty-basic, as it is capable of interpreting the MONEY.BAS program that was part of QBasic

MONEY.BAS

If you have a copy of MONEY.BAS lying around you can give it a try. You'll need to convert it to UTF8 first, as rusty-basic can't handle OEM/ASCII charset yet. Everything should work as it did in QBasic, with the exception of printer support which isn't implemented. I've only tested it on Windows, but the crossterm library used for the console should work on multiple environments.

Added

  • Support for SHARED variables
  • Implemented GOSUB, RETURN and END
  • Implemented EXIT SUB and EXIT FUNCTION
  • Implemented RESUME
  • Support for multiple expressions in a single CASE statement
  • Implement STATIC subs
  • Support for multiple variables in a single DIM statement
  • Implemented DO loop
  • Implemented modulo (MOD) operator
  • Support for random mode files
  • Implemented VIEW PRINT
  • Implemented LOCATE
  • Implemented DATA and READ
  • Stub implementation for WIDTH
  • Implemented built-in functions STRING$, LEFT$, RIGHT$, LCASE$, UCASE$, CVD, MKD$, LTRIM$, RTRIM$, INKEY$, ERR, SPACE$
  • Implemented memory manipulation statements DEF SEG, PEEK, POKE, VARPTR, VARSEG
  • Implemented CLS and COLOR
  • Implemented REDIM
  • Implemented BEEP (only for Windows)

Bugfixes

  • Supporting keywords as string variable names (e.g. END$ is allowed, END% is not allowed)
  • Allowing whitespace after sub name
  • Fixed bug with parsing IF statements with expressions in parenthesis
  • Allowing sub/function declarations to be left unimplemented (as long as they're not used, QBasic doesn't seem to mind)
  • Fixed bugs in priority of binary expressions
  • Support array elements and user defined properties for LINE INPUT
  • Trimming whitespace in INPUT
  • Support more formatting characters in PRINT USING, namely \, !, and the thousands separator ,.
  • LEN function was reporting UTF8 byte length, switched to ASCII to ensure 1 byte = 1 char expectation of QBasic
  • STR$ function should print leading space for non-negative numbers

v0.5.0

27 Nov 13:32
Compare
Choose a tag to compare

The highlight of version 0.5.0 is the support for arrays!

Summary

Added

  • Support for arrays
  • Implemented print zones and limited support for format strings
  • CLOSE without arguments
  • Octal and hexadecimal literals
  • Support for fixed length string variables

Fixed

  • Ensuring for loop variable is numeric
  • Bugfix: arguments to subprograms could appear in random order, due to usage of HashMap in the implementation

Details

Array support

The most important feature in this release is the support for arrays. It is possible to define (DIM) arrays of multiple dimensions, read and write array elements, even pass arrays as parameters.

DIM squares(1 TO 10)
FOR I = 1 TO 10
    squares(I) = I * I 
NEXT

Mind the parenthesis when passing an array as parameter:

DECLARE SUB PrintArray(numbers%())

DIM numbers%(1 TO 5)
PrintArray numbers%()

SUB PrintArray(numbers%())
    FOR I = LBOUND(numbers%) TO UBOUND(numbers%)
        PRINT numbers%(I)
    NEXT
END SUB

Implementing LBOUND and UBOUND I found an inconsistency (in my opinion) in QBasic. When passing an array to a user defined function / sub, the empty parenthesis following its name are required. When passing an array to LBOUND / UBOUND functions, the parenthesis must not be used (as shown in the above example). As always, rusty_basic strives to not diverge from QBasic implementation.

Print zones

The syntax of the PRINT statement is rather complicated and custom. Arguments to functions or subs are separated by commas. In PRINT, the comma is more than a separator. It determines that the output of the next argument will happen on the next print zone. Print zones are 14 characters wide.

It is possible to use a semicolon instead of a comma, in which case the argument will be printed immediately after the last value.

Consider these two examples:

PRINT "a", "b"
PRINT "a"; "b"

The first line will print "a", then 13 spaces, then "b".
The second line will print "ab", without any space between them.

It gets more complicated when the arguments are numbers, where an implicit whitespace is added if the number is positive. I don't remember it off the top of my head but I sure wrote lots of unit tests for it :-D

It's even possible to mix and match commas, semicolons, have commas followed by commas without any argument in between, and so on. This required a special parser implementation just for the PRINT statement, which is too different from the rest (especially because a comma is something significant that needs to be preserved at runtime).

Format strings

Another addition to the PRINT statement is the PRINT USING variation. This allows the user to control the output of an expression with a format string e.g.

PRINT USING "###.##"; 123.456

will print 123.46 (rounding to two digits after the decimal point).

The documentation doesn't quite specify what will happen if the number of placeholders in the format string doesn't match the number of passed arguments. Consider the following code:

PRINT USING "A: # B: # C"; 1, 2, 3

Note that there are 2 placeholders but 3 arguments. In this case it seems printing will start over but stop as soon as it run out of arguments, so it prints "A: 1 B: 2 CA: 3 B: ".

This feature is partially implemented, covering only the # placeholder. The documentation mentions more placeholders, but it's quite unclear to me how they're supposed to behave.

CLOSE without arguments

Implemented support for calling CLOSE without arguments, which closes all open files.

Octal and hexadecimal literals

It's now possible to use octal PRINT &O10 and hexadecimal PRINT &HFF literals.

Support for fixed length variables

It's now possible to define a variable of a fixed length string. This was previously implemented only for elements of user defined types.

DIM PostCode AS STRING * 10

It is not possible in QBasic to have a function / sub parameter in this way. But it is possible to pass a fixed length string as a parameter (and it's by reference) to a sub that expects a string:

DIM TwoLetters AS STRING * 2
TwoLetters = "AB"
PRINT TwoLetters
Modify TwoLetters
PRINT TwoLetters

SUB Modify(X$)
    PRINT X$
    X$ = "XYZ"
    PRINT X$
END SUB

The above program prints:

AB
AB
XYZ
XY

So the fixed length string variable is passed by reference (or sort of) but while it's inside the sub it can grow to more than its declared length. Immediately after exiting the sub, it's trimmed down to 2 characters. This surprising discovery took some time to implement and affected the way parameter passing is implemented in general in rusty_basic.

v0.4.0

19 Sep 14:21
Compare
Choose a tag to compare

New features in v0.4.0:

  • Support for user defined types
  • Support for variable names containing periods
  • Support DIM var-name AS var-type syntax for built-in types (integer, long, single, double, string)

Fixes:

  • Limiting identifiers to 40 characters
  • Ensuring file numbers are between 1 and 255

On the technical side, the parser was practically rewritten using parsing combinators.

v0.3.0

29 Jul 06:52
Compare
Choose a tag to compare

This is the first release after moving rusty-basic to its own repository.

Milestone: rusty-basic is able to run the TODO.BAS program originally mentioned in this blog post (modified for QBasic).