Skip to content

Latest commit

 

History

History
1788 lines (1382 loc) · 37.5 KB

File metadata and controls

1788 lines (1382 loc) · 37.5 KB

Sysclone QBasic Compatibility Reference

Auto-generated from Truth Vectors. This document serves as the absolute specification for MS-DOS QBasic behavior.

🔠 Alphabetical Index

/ (Floating-Point Division)\ (Integer Division)ABS FunctionASC FunctionATN FunctionCALL StatementCHR$ FunctionCINT FunctionCOS FunctionDefault Implicit Typing (SINGLE)DEFINT StatementDEFSNG StatementDO...LOOP Statement (Post-test)DO...LOOP Statement (Pre-test)ERASE StatementEXIT FOR StatementEXP FunctionExplicit Type Suffix OverrideFIX FunctionFixed-Length String PaddingFixed-Length String TruncationFOR...NEXT StatementGOSUB...RETURN StatementHEX$ FunctionINSTR FunctionINT FunctionLCASE$ FunctionLEFT$ FunctionLEN FunctionLOG FunctionLTRIM$ FunctionMemory Aliasing (DIM AS STRING)MID$ FunctionMOD OperatorRIGHT$ FunctionRTRIM$ FunctionSELECT CASE StatementSIN FunctionSPACE$ FunctionSQR FunctionSTR$ FunctionSTRING$ FunctionSWAP StatementTAN FunctionUCASE$ FunctionVAL FunctionWHILE...WEND Statement


📚 Thematic Contents


STDLIB: Math & Floating-Point Unit

INT Function

Syntax: INT(numeric_expression)

Returns the largest integer less than or equal to a numeric expression.

Example:

PRINT INT(2.8)

Output:

 2 

🔬 Hardware Quirks & Edge Cases

For negative numbers, it mathematically floors downwards (e.g., -2.2 becomes -3).

IntPos = INT(2.8)
IntNeg = INT(-2.2)

Memory State (End of Execution):

IntPos = 2            (number)
IntNeg = -3           (number)

FIX Function

Syntax: FIX(numeric_expression)

Returns the truncated integer part of a numeric expression.

Example:

PRINT FIX(-2.8)

Output:

-2 

🔬 Hardware Quirks & Edge Cases

Unlike INT, it strictly removes the fractional part towards zero without flooring downwards.

FixPos = FIX(2.8)
FixNeg = FIX(-2.8)

Memory State (End of Execution):

FixPos = 2            (number)
FixNeg = -2           (number)

CINT Function

Syntax: CINT(numeric_expression)

Rounds a numeric expression to the nearest integer.

Example:

PRINT CINT(2.6)

Output:

 3 

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRK: QBasic employs 'Banker's Rounding'. If the fractional part is exactly .5, it rounds to the nearest EVEN integer.

CintUp = CINT(2.6)
CintHalfEven = CINT(2.5)
CintHalfOdd = CINT(3.5)

Memory State (End of Execution):

CintUp       = 3            (number)
CintHalfEven = 2            (number)
CintHalfOdd  = 4            (number)

/ (Floating-Point Division)

Syntax: expression1 / expression2

Divides two numbers and returns a floating-point result.

Example:

PRINT 5 / 2

Output:

 2.5 

🔬 Hardware Quirks & Edge Cases

Operands are not rounded before the operation, and the result retains fractional precision.

FloatDiv! = 5 / 2

Memory State (End of Execution):

FloatDiv! = 2.5          (number)

\ (Integer Division)

Syntax: expression1 \ expression2

Divides two numbers and returns an integer result.

Example:

PRINT 5 \ 2

Output:

 2 

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRK: Both operands are rounded to integers (Banker's Rounding) BEFORE the division occurs. Using float suffix (!) prevents DEFINT masking during tests. Evaluates rounding of .5 to the nearest even integer.

IntDiv! = 5 \ 2
IntDivRound! = 5.6 \ 1.9
IntDivBankerEven! = 2.5 \ 1
IntDivBankerOdd! = 3.5 \ 1

Memory State (End of Execution):

IntDiv!           = 2            (number)
IntDivRound!      = 3            (number)
IntDivBankerEven! = 2            (number)
IntDivBankerOdd!  = 4            (number)

MOD Operator

Syntax: expression1 MOD expression2

Divides two numbers and returns the integer remainder.

Example:

PRINT 10 MOD 3

Output:

 1 

🔬 Hardware Quirks & Edge Cases

Like the backslash operator, MOD rounds its operands to integers BEFORE performing the operation. The result inherits the sign of the first operand. Using float suffix (!) prevents DEFINT masking during tests.

ModNorm! = 10 MOD 3
ModNeg! = -10 MOD 3
ModRound! = 10.6 MOD 3

Memory State (End of Execution):

ModNorm!  = 1            (number)
ModNeg!   = -1           (number)
ModRound! = 2            (number)

ABS Function

Syntax: ABS(numeric_expression)

Returns the absolute value of a numeric expression.

Example:

PRINT ABS(-42)

Output:

 42 

🔬 Hardware Quirks & Edge Cases

Standard arithmetic absolute conversion.

AbsVal = ABS(-42)

Memory State (End of Execution):

AbsVal = 42           (number)

SQR Function

Syntax: SQR(numeric_expression)

Returns the square root of a numeric expression.

Example:

PRINT SQR(16)

Output:

 4 

🔬 Hardware Quirks & Edge Cases

Passing a negative number natively throws an 'Illegal function call' exception.

SqrVal = SQR(16)

Memory State (End of Execution):

SqrVal = 4            (number)

SIN Function

Syntax: SIN(angle)

Returns the trigonometric sine of an angle (in radians).

Example:

PRINT SIN(0)

Output:

 0 

🔬 Hardware Quirks & Edge Cases

To avoid floating-point precision mismatch assertions between MS-DOS x87 architecture and modern V8 IEEE-754, the test result is multiplied by 1000 and clamped to an integer.

SinZero = CINT(SIN(0) * 1000)

Memory State (End of Execution):

SinZero = 0            (number)

COS Function

Syntax: COS(angle)

Returns the trigonometric cosine of an angle (in radians).

Example:

PRINT COS(0)

Output:

 1 

🔬 Hardware Quirks & Edge Cases

To avoid floating-point precision mismatch assertions between MS-DOS x87 architecture and modern V8 IEEE-754, the test result is multiplied by 1000 and clamped to an integer.

CosZero = CINT(COS(0) * 1000)

Memory State (End of Execution):

CosZero = 1000         (number)

TAN Function

Syntax: TAN(angle)

Returns the trigonometric tangent of an angle (in radians).

Example:

PRINT TAN(0)

Output:

 0 

🔬 Hardware Quirks & Edge Cases

To avoid floating-point precision mismatch assertions between MS-DOS x87 architecture and modern V8 IEEE-754, the test result is multiplied by 1000 and clamped to an integer.

TanZero = CINT(TAN(0) * 1000)

Memory State (End of Execution):

TanZero = 0            (number)

ATN Function

Syntax: ATN(numeric_expression)

Returns the arctangent of a numeric expression (in radians).

Example:

PRINT ATN(0)

Output:

 0 

🔬 Hardware Quirks & Edge Cases

To avoid floating-point precision mismatch assertions between MS-DOS x87 architecture and modern V8 IEEE-754, the test result is multiplied by 1000 and clamped to an integer.

AtnZero = CINT(ATN(0) * 1000)

Memory State (End of Execution):

AtnZero = 0            (number)

EXP Function

Syntax: EXP(numeric_expression)

Returns 'e' (the base of natural logarithms) raised to the power of the specified expression.

Example:

PRINT EXP(0)

Output:

 1 

🔬 Hardware Quirks & Edge Cases

To avoid floating-point precision mismatch assertions between MS-DOS x87 architecture and modern V8 IEEE-754, the test result is multiplied by 1000 and clamped to an integer.

ExpZero = CINT(EXP(0) * 1000)

Memory State (End of Execution):

ExpZero = 1000         (number)

LOG Function

Syntax: LOG(numeric_expression)

Returns the natural logarithm of a numeric expression.

Example:

PRINT LOG(1)

Output:

 0 

🔬 Hardware Quirks & Edge Cases

To avoid floating-point precision mismatch assertions between MS-DOS x87 architecture and modern V8 IEEE-754, the test result is multiplied by 1000 and clamped to an integer.

LogOne = CINT(LOG(1) * 1000)

Memory State (End of Execution):

LogOne = 0            (number)

STDLIB: Strings & Type Casting

STR$ Function

Syntax: STR$(numeric_expression)

Returns a string representation of the value of a numeric expression.

Example:

PRINT STR$(42)

Output:

 42

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRK: Positive numbers and zero are strictly prefixed with a leading space (acting as an implicit '+' sign). Negative numbers are prefixed by '-' without a leading space.

PosStr$ = STR$(42)
ZeroStr$ = STR$(0)
NegStr$ = STR$(-15)

Memory State (End of Execution):

PosStr$  = " 42"        (string)
ZeroStr$ = " 0"         (string)
NegStr$  = "-15"        (string)

HEX$ Function

Syntax: HEX$(numeric_expression)

Returns a string that represents the hexadecimal value of the decimal argument.

Example:

PRINT HEX$(255)

Output:

FF

🔬 Hardware Quirks & Edge Cases

QBasic rounds floats to the nearest integer before conversion (Banker's rounding). Standard numbers are treated as 16-bit integers (FFFF for -1), while Long integers (&) or values exceeding -32768 fallback to 32-bit two's complement. Output is always uppercase.

NormHex$ = HEX$(255)
RoundDownHex$ = HEX$(12.4)
RoundUpHex$ = HEX$(12.6)
NegTwosComp$ = HEX$(-1)
LargeNegTwosComp$ = HEX$(-100000)
ZeroHex$ = HEX$(0)

Memory State (End of Execution):

NormHex$          = "FF"         (string)
RoundDownHex$     = "C"          (string)
RoundUpHex$       = "D"          (string)
NegTwosComp$      = "FFFF"       (string)
LargeNegTwosComp$ = "FFFE7960"   (string)
ZeroHex$          = "0"          (string)

VAL Function

Syntax: VAL(stringexpression$)

Returns the numeric value of a string of characters.

Example:

PRINT VAL("123.45")

Output:

123.45

🔬 Hardware Quirks & Edge Cases

Accurately parses MS-DOS legacy radix prefixes: Hexadecimal (&H) and Octal (&O). Invalid input gracefully falls back to 0 without throwing an exception.

NormVal! = VAL("123.45")
HexVal = VAL("&H10")
HexLowVal = VAL("&hc")
OctVal = VAL("&O10")
GarbageVal = VAL("NOTANUMBER")

Memory State (End of Execution):

NormVal!   = 123.45       (number)
HexVal     = 16           (number)
HexLowVal  = 12           (number)
OctVal     = 8            (number)
GarbageVal = 0            (number)

LEFT$ Function

Syntax: LEFT$(stringexpression$, n%)

Returns a string consisting of the leftmost n% characters of a string.

Example:

PRINT LEFT$("SYSCLONE", 3)

Output:

SYS

🔬 Hardware Quirks & Edge Cases

If N exceeds the string length, the entire string is safely returned without throwing an out-of-bounds error.

BaseStr$ = "SYSCLONE"
NormLeft$ = LEFT$(BaseStr$, 3)
OverLeft$ = LEFT$(BaseStr$, 50)

Memory State (End of Execution):

NormLeft$ = "SYS"        (string)
OverLeft$ = "SYSCLONE"   (string)

RIGHT$ Function

Syntax: RIGHT$(stringexpression$, n%)

Returns a string consisting of the rightmost n% characters of a string.

Example:

PRINT RIGHT$("SYSCLONE", 5)

Output:

CLONE

🔬 Hardware Quirks & Edge Cases

Similar to LEFT$, an oversized N returns the whole string safely.

BaseStr$ = "SYSCLONE"
NormRight$ = RIGHT$(BaseStr$, 5)
OverRight$ = RIGHT$(BaseStr$, 50)

Memory State (End of Execution):

NormRight$ = "CLONE"      (string)
OverRight$ = "SYSCLONE"   (string)

MID$ Function

Syntax: MID$(stringexpr$, start%[, length%])

Returns a substring of a specified length starting at a 1-based index.

Example:

PRINT MID$("SYSCLONE", 4, 2)

Output:

CL

🔬 Hardware Quirks & Edge Cases

If the length parameter is omitted, it returns the remainder of the string. Out-of-bounds start indices yield an empty string without throwing an error.

BaseStr$ = "SYSCLONE"
NormMid$ = MID$(BaseStr$, 4, 2)
NoLenMid$ = MID$(BaseStr$, 4)
OutBoundsMid$ = MID$(BaseStr$, 99, 2)

Memory State (End of Execution):

NormMid$      = "CL"         (string)
NoLenMid$     = "CLONE"      (string)
OutBoundsMid$ = ""           (string)

SPACE$ Function

Syntax: SPACE$(n%)

Returns a string of n% spaces.

Example:

PRINT "A" + SPACE$(3) + "B"

Output:

A   B

🔬 Hardware Quirks & Edge Cases

Negative or zero lengths safely return an empty string.

SpcStr$ = SPACE$(3)

Memory State (End of Execution):

SpcStr$ = "   "        (string)

STRING$ Function

Syntax: STRING$(n%, {stringexpression$ | ascii_code%})

Returns a string of characters, n% long, consisting of a single character.

Example:

PRINT STRING$(4, "A")

Output:

AAAA

🔬 Hardware Quirks & Edge Cases

The character can be specified either as a numeric ASCII/CP437 code, or as a string (in which case only the first character is used).

StrChar$ = STRING$(4, "A")
StrLong$ = STRING$(3, "XYZ")
StrCode$ = STRING$(3, 65)

Memory State (End of Execution):

StrChar$ = "AAAA"       (string)
StrLong$ = "XXX"        (string)
StrCode$ = "AAA"        (string)

LEN Function

Syntax: LEN(stringexpression$)

Returns the number of characters in a string.

Example:

PRINT LEN("HELLO")

Output:

5

🔬 Hardware Quirks & Edge Cases

Standard length calculation.

StrLen = LEN("HELLO")

Memory State (End of Execution):

StrLen = 5            (number)

UCASE$ Function

Syntax: UCASE$(stringexpression$)

Returns a string expression with all letters converted to uppercase.

Example:

PRINT UCASE$("hello")

Output:

HELLO

🔬 Hardware Quirks & Edge Cases

Transforms lowercase ASCII characters to uppercase without affecting symbols or numbers.

UpStr$ = UCASE$("hello")

Memory State (End of Execution):

UpStr$ = "HELLO"      (string)

LCASE$ Function

Syntax: LCASE$(stringexpression$)

Returns a string expression with all letters converted to lowercase.

Example:

PRINT LCASE$("HELLO")

Output:

hello

🔬 Hardware Quirks & Edge Cases

Transforms uppercase ASCII characters to lowercase without affecting symbols or numbers.

LowStr$ = LCASE$("HELLO")

Memory State (End of Execution):

LowStr$ = "hello"      (string)

LTRIM$ Function

Syntax: LTRIM$(stringexpression$)

Returns a copy of a string with leading (leftmost) spaces removed.

Example:

PRINT LTRIM$("  TEXT")

Output:

TEXT

🔬 Hardware Quirks & Edge Cases

Crucial for cleaning up STR$ outputs which naturally contain leading spaces for positive numbers.

LTrimStr$ = LTRIM$("  TEXT")

Memory State (End of Execution):

LTrimStr$ = "TEXT"       (string)

RTRIM$ Function

Syntax: RTRIM$(stringexpression$)

Returns a copy of a string with trailing (rightmost) spaces removed.

Example:

PRINT RTRIM$("TEXT  ")

Output:

TEXT

🔬 Hardware Quirks & Edge Cases

Often used to trim fixed-length strings (STRING * N) that are padded with spaces on the right.

RTrimStr$ = RTRIM$("TEXT  ")

Memory State (End of Execution):

RTrimStr$ = "TEXT"       (string)

CHR$ Function

Syntax: CHR$(ascii_code%)

Returns a one-character string whose ASCII/CP437 code is the argument.

Example:

PRINT CHR$(65)

Output:

A

🔬 Hardware Quirks & Edge Cases

Sysclone maps these directly to the MS-DOS Code Page 437 (CP437) for graphical block character fidelity.

Char$ = CHR$(65)

Memory State (End of Execution):

Char$ = "A"          (string)

ASC Function

Syntax: ASC(stringexpression$)

Returns the ASCII/CP437 character code corresponding to the first letter in a string.

Example:

PRINT ASC("A")

Output:

65

🔬 Hardware Quirks & Edge Cases

If the string is empty, true QBasic throws an 'Illegal function call' error.

AscVal = ASC("A")

Memory State (End of Execution):

AscVal = 65           (number)

INSTR Function

Syntax: INSTR([start%,] string1$, string2$)

Returns the character position of the first occurrence of a string in another.

Example:

PRINT INSTR("HELLO WORLD", "WORLD")

Output:

7

🔬 Hardware Quirks & Edge Cases

Uses 1-based indexing. If an optional start index is provided, the search begins there. Returns 0 if the substring is not found or if the start index is out of bounds.

FoundIdx = INSTR("HELLO WORLD", "WORLD")
OffsetIdx = INSTR(3, "HELLO WORLD, HELLO", "HELLO")
NotFoundIdx = INSTR("HELLO", "Z")
OutBoundsIdx = INSTR(100, "HELLO", "L")

Memory State (End of Execution):

FoundIdx     = 7            (number)
OffsetIdx    = 14           (number)
NotFoundIdx  = 0            (number)
OutBoundsIdx = 0            (number)

CORE: Control Flow & Jumps

FOR...NEXT Statement

Syntax: FOR counter = start TO end [STEP increment] ... NEXT [counter]

Iterates a block of code based on a counter variable. The loop continues until the counter passes the terminal value.

Example:

FOR I = 1 TO 3
  PRINT I;
NEXT

Output:

 1  2  3 

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRKS:

  1. Terminal Overshoot: The loop iterator always increments past the limit, leaving the leaked value in memory.
  2. Bound Immutability: The TO and STEP expressions are evaluated exactly ONCE upon entering the loop. Mutating their source variables inside the loop has no effect.
  3. Iterator Mutation: Modifying the counter explicitly inside the loop directly affects the remaining cycles.
' 1. Terminal Overshoot (Positive & Negative)
CountA = 0: FOR I = 1 TO 5: CountA = CountA + 1: NEXT
CountB = 0: FOR J = 5 TO 1 STEP -1: CountB = CountB + 1: NEXT

' 2. Bound Immutability
Limit = 3: Inc = 1: Runs = 0
FOR K = 1 TO Limit STEP Inc
  Runs = Runs + 1
  Limit = 10
  Inc = 5
NEXT

' 3. Iterator Mutation
MutRuns = 0
FOR M = 1 TO 5
  MutRuns = MutRuns + 1
  M = M + 1
NEXT

Memory State (End of Execution):

CountA  = 5            (number)
I       = 6            (number)
CountB  = 5            (number)
J       = 0            (number)
Runs    = 3            (number)
K       = 4            (number)
Limit   = 10           (number)
Inc     = 5            (number)
MutRuns = 3            (number)
M       = 7            (number)

EXIT FOR Statement

Syntax: EXIT FOR

Immediately terminates the execution of the innermost FOR...NEXT loop, transferring control to the statement following the NEXT keyword.

Example:

FOR I = 1 TO 5
  IF I = 3 THEN EXIT FOR
  PRINT I;
NEXT

Output:

 1  2 

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRKS:

  1. Iterator Retention: Unlike a naturally completed loop which overshoots, exiting prematurely leaves the iterator at its exact current value.
  2. Scope Isolation: In nested loops, EXIT FOR strictly breaks only the deepest active loop without affecting outer iterators.
  3. Short-Circuit: Any statements remaining in the loop body after the EXIT FOR command are immediately bypassed.
' 1. Basic Exit and Iterator Retention
TargetVal = 0: FinalI = 0
FOR I = 5 TO 1 STEP -1
  IF I = 3 THEN
    TargetVal = I
    EXIT FOR
  END IF
NEXT
FinalI = I

' 2. Scope Isolation (Nested Loops)
OuterCount = 0: InnerCount = 0
FOR Outer = 1 TO 3
  OuterCount = OuterCount + 1
  FOR Inner = 1 TO 5
    IF Inner = 2 THEN EXIT FOR
    InnerCount = InnerCount + 1
  NEXT
NEXT
FinalOuter = Outer
FinalInner = Inner

' 3. Short-Circuit Execution
Flag = 0: FinalK = 0
FOR K = 1 TO 5
  EXIT FOR
  Flag = 99
NEXT
FinalK = K

Memory State (End of Execution):

TargetVal  = 3            (number)
FinalI     = 3            (number)
OuterCount = 3            (number)
FinalOuter = 4            (number)
InnerCount = 3            (number)
FinalInner = 2            (number)
Flag       = 0            (number)
FinalK     = 1            (number)

DO...LOOP Statement (Pre-test)

Syntax: DO [{WHILE | UNTIL} condition] ... LOOP

Evaluates the condition BEFORE executing the block. If the initial condition is unmet, the loop executes 0 times. Truthiness is strict: exactly 0 is FALSE, any other number is TRUE.

Example:

Cond = 0
DO WHILE Cond
  PRINT "Never prints"
LOOP

Output:


🔬 Hardware Quirks & Edge Cases

Proves that a pre-test loop bypasses the block entirely if the condition is false (WHILE 0) or already true (UNTIL -1). Also validates arbitrary float truthiness.

' 1. WHILE False -> 0 runs
RunsPreWhile = 0
DO WHILE 0
  RunsPreWhile = RunsPreWhile + 1
LOOP

' 2. UNTIL True -> 0 runs
RunsPreUntil = 0
DO UNTIL -1
  RunsPreUntil = RunsPreUntil + 1
LOOP

' 3. WHILE Arbitrary Float (True) -> 1 run
RunsFloat = 0
CondF = 42.5
DO WHILE CondF
  RunsFloat = RunsFloat + 1
  CondF = 0
LOOP

Memory State (End of Execution):

RunsPreWhile = 0            (number)
RunsPreUntil = 0            (number)
RunsFloat    = 1            (number)

DO...LOOP Statement (Post-test)

Syntax: DO ... LOOP [{WHILE | UNTIL} condition]

Evaluates the condition AFTER executing the block. The loop is guaranteed to execute AT LEAST ONCE, regardless of the initial condition.

Example:

Cond = 0
DO
  PRINT "Prints exactly once"
LOOP WHILE Cond

Output:

 Prints exactly once 

🔬 Hardware Quirks & Edge Cases

Proves the post-test execution flow. A WHILE condition initialized to 0 (FALSE) will still allow exactly one iteration before exiting.

RunsPostA = 0
DO
  RunsPostA = RunsPostA + 1
LOOP WHILE 0

RunsPostB = 0
DO
  RunsPostB = RunsPostB + 1
LOOP UNTIL -1

Memory State (End of Execution):

RunsPostA = 1            (number)
RunsPostB = 1            (number)

GOSUB...RETURN Statement

Syntax: GOSUB label ... label: ... RETURN

Branches to, and returns from, a subroutine within the same module. Variables are fully shared with the caller.

Example:

GOSUB PrintMsg
END
PrintMsg:
  PRINT "Hello"
RETURN

Output:

Hello

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRKS:

  1. Scope Sharing: GOSUB does not create a new variable scope. It shares the exact same memory space as the caller.
  2. Internal Jumps: A GOTO inside a GOSUB block strictly shifts the internal pointer without breaking the GOSUB loop.
  3. Spatial Return: The RETURN statement is strictly required to escape the GOSUB isolation loop and resume main execution.
GlobalVar = 10: StepId = 0
GOSUB RoutineStart
StepId = 99
GOTO EndTest

RoutineStart:
  GlobalVar = GlobalVar * 2
  GOTO RoutineSkip
  GlobalVar = 0 ' MUST NEVER EXECUTE
RoutineSkip:
  StepId = 1
RETURN

EndTest:

Memory State (End of Execution):

GlobalVar = 20           (number)
StepId    = 99           (number)

SELECT CASE Statement

Syntax: SELECT CASE testexpression ... CASE {expressionlist | IS relation | start TO end} ... [CASE ELSE] ... END SELECT

Executes one of several blocks of statements depending on the value of an expression. Supports exact matches, inclusive ranges (TO), and relational comparisons (IS).

Example:

Score = 85
SELECT CASE Score
  CASE IS >= 90
    PRINT "Grade: A"
  CASE 80 TO 89
    PRINT "Grade: B"
  CASE 0, 1, 2
    PRINT "Grade: Z"
  CASE ELSE
    PRINT "Unknown"
END SELECT

Output:

Grade: B

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRKS:

  1. Single Evaluation: The base test expression is evaluated exactly once.
  2. No Fallthrough: Unlike C or JavaScript, QBasic executes ONLY the first matching CASE block and then exits the SELECT entirely.
  3. Range Matching: The 'TO' keyword denotes an inclusive mathematical range.
  4. Relational Matching: The 'IS' keyword allows direct relational comparisons (>, <, >=, <=, <>).
' 1. Exact Match, Range (TO), and No Fallthrough
TestVal = 5: Result = 0: ExecCount = 0
SELECT CASE TestVal
  CASE 1, 2, 3
    Result = 1: ExecCount = ExecCount + 1
  CASE 4 TO 6
    Result = 2: ExecCount = ExecCount + 1
  CASE 5
    Result = 3: ExecCount = ExecCount + 1 ' MUST NEVER EXECUTE (No fallthrough)
  CASE ELSE
    Result = 4: ExecCount = ExecCount + 1
END SELECT

' 2. Relational Matching (CASE IS)
Score = 85: Grade = 0
SELECT CASE Score
  CASE IS >= 90
    Grade = 1
  CASE IS >= 80
    Grade = 2
  CASE IS < 50
    Grade = 3
END SELECT

Memory State (End of Execution):

Result    = 2            (number)
ExecCount = 1            (number)
Grade     = 2            (number)

WHILE...WEND Statement

Syntax: WHILE condition ... WEND

Executes a series of statements as long as a specified condition is true. It is the legacy predecessor to DO WHILE...LOOP.

Example:

I = 1
WHILE I < 4
  PRINT I;
  I = I + 1
WEND

Output:

 1  2  3 

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRKS:

  1. No Native Exit: Unlike DO or FOR, QBasic does not support 'EXIT WHILE'. Early termination must be achieved via GOTO or RETURN.
  2. Pre-test Only: The condition is evaluated before the loop begins. If initially false (0), the body executes 0 times.
Runs = 0: ValA = 1
WHILE ValA
  Runs = Runs + 1
  IF Runs = 2 THEN GOTO EscapeWhile
WEND
EscapeWhile:

FalseRuns = 0
WHILE 0
  FalseRuns = 99
WEND

Memory State (End of Execution):

Runs      = 2            (number)
FalseRuns = 0            (number)

CORE: Memory, Types & Structures

Default Implicit Typing (SINGLE)

Syntax: Variable = value

By default, QBasic treats all undeclared variables without a type suffix as Single-precision floats (!). They retain fractional values without rounding.

Example:

DefaultFloat = 3.14
PRINT DefaultFloat

Output:

 3.14 

🔬 Hardware Quirks & Edge Cases

Ensures the engine natively defaults to Single precision when no DEF type or suffix is active.

DefaultFloat = 3.14

Memory State (End of Execution):

DefaultFloat = 3.14         (number)

DEFINT Statement

Syntax: DEFINT letter[-letter]

Sets the default data type for variables to Integer.

Example:

DEFINT A-Z
Score = 10.8
PRINT Score

Output:

 11 

🔬 Hardware Quirks & Edge Cases

Forces all untyped variables starting with specified letters to automatically become integers. Floating-point assignments to these variables are mathematically rounded (Banker's rounding).

DEFINT A-Z
ImplicitIntA = 2.5
ImplicitIntB = 2.6

Memory State (End of Execution):

ImplicitIntA = 2            (number)
ImplicitIntB = 3            (number)

DEFSNG Statement

Syntax: DEFSNG letter[-letter]

Sets the default data type for variables to Single-precision float.

Example:

DEFSNG A-Z
Score = 10.8
PRINT Score

Output:

 10.8 

🔬 Hardware Quirks & Edge Cases

Forces untyped variables to default to Single-precision. This preserves fractional values without mathematical rounding. While QBasic defaults to Single precision natively, DEFSNG is used to explicitly override a previous DEFINT.

DEFINT A-Z
DEFSNG A-Z
ImplicitSng = 5.8

Memory State (End of Execution):

ImplicitSng = 5.8          (number)

Explicit Type Suffix Override

Syntax: Variable[%, &, !, #, $]

Explicit type declaration suffixes.

Example:

DEFINT A-Z
PRINT 5.5!

Output:

 5.5 

🔬 Hardware Quirks & Edge Cases

Even if DEFINT is active, appending a specific type suffix (like ! for Single Precision Float) overrides the implicit rule and preserves the fractional value.

DEFINT A-Z
OverrideFloat! = 3.14

Memory State (End of Execution):

OverrideFloat! = 3.14         (number)

Memory Aliasing (DIM AS STRING)

Syntax: DIM variable AS type

Declares a variable and allocates memory space.

Example:

DIM Name AS STRING
Name = "NIBBLES"
PRINT Name$

Output:

NIBBLES

🔬 Hardware Quirks & Edge Cases

Declaring a variable with an explicit type (e.g., AS STRING) internally links it to its MS-DOS suffixed counterpart. Writing to the base name updates the suffixed name, as they share the exact same memory pointer.

DIM AliasTest AS STRING
AliasTest = "NIBBLES"
Extracted$ = AliasTest$

Memory State (End of Execution):

Extracted$ = "NIBBLES"    (string)

Fixed-Length String Padding

Syntax: DIM variable AS STRING * length

Declares a string with a fixed memory length.

Example:

DIM S AS STRING * 5
S = "HI"
PRINT S; "THERE"

Output:

HI   THERE

🔬 Hardware Quirks & Edge Cases

Allocating a Fixed-Length String locks a specific memory block. If a shorter string is assigned, it is automatically padded with spaces on the right to fill the block.

DIM PadStr AS STRING * 5
PadStr = "HI"
PadLen = LEN(PadStr)

Memory State (End of Execution):

PadStr = "HI   "      (string)
PadLen = 5            (number)

Fixed-Length String Truncation

Syntax: DIM variable AS STRING * length

Prevents memory overflow during assignment.

Example:

DIM S AS STRING * 3
S = "123456"
PRINT S

Output:

123

🔬 Hardware Quirks & Edge Cases

If a string longer than the locked memory block is assigned, MS-DOS silently truncates the overflow without throwing an error.

DIM TruncStr AS STRING * 3
TruncStr = "123456"

Memory State (End of Execution):

TruncStr = "123"        (string)

SWAP Statement

Syntax: SWAP variable1, variable2

Exchanges the values of two variables of the same type. Works with scalars, array elements, and UDT properties.

Example:

A = 1: B = 2
SWAP A, B
PRINT A; B

Output:

 2  1 

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRKS:

  1. Memory Type Integrity: SWAP must work seamlessly across direct environment variables and complex array indices.
  2. Reference Decoupling: In memory-managed languages (like JS), swapping object references directly can entangle them. A purist VM must deep-clone the values to safely exchange them.
  3. Fixed-Length Preservation: Swapping UDTs or Fixed-Length Strings must mutate the memory IN PLACE, otherwise the fixed-length constraint is permanently destroyed.
' 1. Scalar Swap
ValA = 10: ValB = 99
SWAP ValA, ValB

' 2. Array Indices Swap
DIM SwapArr(2)
SwapArr(1) = 42: SwapArr(2) = 77
SWAP SwapArr(1), SwapArr(2)
ArrRes1 = SwapArr(1)
ArrRes2 = SwapArr(2)

' 3. Fixed-Length String Swap Integrity
TYPE SwapType
  F AS STRING * 4
END TYPE
DIM ST1 AS SwapType, ST2 AS SwapType
ST1.F = "A"
ST2.F = "B"
SWAP ST1, ST2
ST1.F = "Z"
FixedLenAfterSwap = LEN(ST1.F)

Memory State (End of Execution):

ValA              = 99           (number)
ValB              = 10           (number)
ArrRes1           = 77           (number)
ArrRes2           = 42           (number)
FixedLenAfterSwap = 4            (number)

ERASE Statement

Syntax: ERASE arrayname [, arrayname] ...

Reinitializes elements of static arrays and completely deallocates dynamic arrays from memory.

Example:

DIM Arr(5)
Arr(1) = 42
ERASE Arr
PRINT Arr(1)

Output:

 0 

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRKS:

  1. Static Array Reset: For statically dimensioned arrays, ERASE does not destroy the array footprint. It iterates and resets numeric arrays to 0, and string arrays to empty strings ("").
  2. Type Protection: Erasing an array of UDTs containing Fixed-Length strings must softly reset the string's content without destroying its prototype length.
' 1. Numeric Array Erase
DIM NumArr(2)
NumArr(1) = 42: NumArr(2) = 99
ERASE NumArr
CheckNum1 = NumArr(1)
CheckNum2 = NumArr(2)

' 2. String Array Erase
DIM StrArr$(1)
StrArr$(1) = "Hello World"
ERASE StrArr$
CheckStr$ = StrArr$(1)

' 3. UDT with Fixed-Length String Erase
TYPE EraseType
  F AS STRING * 4
END TYPE
DIM EArr(1) AS EraseType
EArr(1).F = "TEST"
ERASE EArr
EArr(1).F = "X"
FixedLenAfterErase = LEN(EArr(1).F)

Memory State (End of Execution):

CheckNum1          = 0            (number)
CheckNum2          = 0            (number)
CheckStr$          = ""           (string)
FixedLenAfterErase = 4            (number)

CORE: Procedures & Functions

CALL Statement

Syntax: CALL subname([argumentlist])

Transfers control to a subroutine. Arguments are passed by reference by default, allowing the subroutine to directly modify the caller's variables.

Example:

A = 10
CALL Multiply(A)
PRINT A
END
SUB Multiply(X)
  X = X * 2
END SUB

Output:

 20 

🔬 Hardware Quirks & Edge Cases

CRITICAL QUIRKS:

  1. Pass by Reference (Default): Passing a raw variable directly links the subroutine's parameter to the caller's memory address.
  2. Pass by Value (Forced): Enclosing a variable in extra parentheses explicitly evaluates it as an expression (an R-Value), forcing a disconnected copy to be passed.
  3. Multiple Parentheses: Nesting parentheses, such as (((X))), must strictly resolve as a single R-Value without corrupting the AST or evaluation.
RefVar = 10
ValVar = 10
DeepVar = 10
CALL ScopeTest(RefVar, (ValVar), (((DeepVar))))
GOTO EndCallTest

SUB ScopeTest (ArgRef, ArgVal, ArgDeep)
  ArgRef = 99
  ArgVal = 99
  ArgDeep = 99
END SUB

EndCallTest:

Memory State (End of Execution):

RefVar  = 99           (number)
ValVar  = 10           (number)
DeepVar = 10           (number)