diff --git a/Makefile b/Makefile index 48b15eb9..16f9749f 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ LIBS = $(sort $(notdir $(patsubst %/Makefile,%,$(wildcard src/libs/kpunch/*/Makefile)))) CORE_LIBS = libdvx libtasks -.PHONY: all clean $(LIBS) loader widgets apps tools installer dist deploy-helpsrc compile-help deploy-sdk test test-quick test-asan test-host test-samples coverage +.PHONY: all clean $(LIBS) loader widgets apps tools installer dist deploy-helpsrc compile-help deploy-sdk test test-quick test-asan test-host test-modules test-samples coverage bench bench-compare # Every build ends by making the release media in dist/ (a second or # two), so dist/ always holds the latest DVX. @@ -85,8 +85,9 @@ deploy-helpsrc: $(LIBS) widgets apps tools # Host-side tests. Every suite is native gcc; the GUI suites build # obj/host/libdvx.a, the widget .so files and libdvxtest.a themselves # (sanitizers on by default, SAN=1). -# make test -- everything: BASIC harnesses + fuzz, sample goldens, -# proxy build, all host suites, tools goldens, lint gate +# make test -- everything: DOS module symbol check, BASIC +# harnesses + fuzz, sample goldens, proxy build, all +# host suites, tools goldens, lint gate # make test-quick -- same minus the fuzzers, static analyzer and tools # make test-asan -- BASIC harnesses rebuilt under ASan/UBSan plus the # host suites (which are already sanitized) @@ -96,6 +97,7 @@ DVXBASIC_DIR = src/apps/kpunch/dvxbasic include src/test/suites.mk test: apps + $(MAKE) test-modules $(MAKE) -C $(DVXBASIC_DIR) tests $(MAKE) -C src/tools/proxy $(MAKE) test-samples @@ -104,12 +106,14 @@ test: apps $(MAKE) -C src/test/lint test-quick: apps + $(MAKE) test-modules $(MAKE) -C $(DVXBASIC_DIR) tests FUZZ_N=0 $(MAKE) test-samples $(MAKE) test-host $(MAKE) -C src/test/lint style ascii test-asan: apps + $(MAKE) test-modules $(MAKE) -C $(DVXBASIC_DIR) tests TESTSAN=1 $(MAKE) test-host @@ -121,6 +125,28 @@ test-host: test-samples: src/test/samples/runSamples.sh +# Every unresolved symbol of every built DOS module (bin/ .lib .wgt .drv +# .app .dxe, plus the BASIC stub that compiled programs are made from) must +# be exported by dvx.exe or by a module loaded before it, or dlopen() fails +# on DOS (src/test/shell/checkDxeExports.sh, src/test/tools/checkModules.py). +# Builds every module first so the check sees the current binaries. +BASIC_STUB = obj/dvxbasic/basstub.app + +test-modules: $(LIBS) loader widgets apps + src/test/shell/checkDxeExports.sh . $(BASIC_STUB) + +# BASIC compiler / VM benchmarks (src/test/basic/basBench.c, programs in +# src/test/basic/bench): results in obj/test/bench/results.txt; +# bench-compare shows the change from BENCH_OLD (default the Phase 0 +# baseline, src/test/basic/bench/baseline.txt) to BENCH_NEW (default the +# last make bench). BENCH_ARGS passes basBench options (-runs N, -profile, +# -filter NAME). +bench: + $(MAKE) -C src/test/basic bench + +bench-compare: + $(MAKE) -C src/test/basic bench-compare $(if $(BENCH_OLD),BENCH_OLD=$(abspath $(BENCH_OLD))) $(if $(BENCH_NEW),BENCH_NEW=$(abspath $(BENCH_NEW))) + # The real DOS build in an emulator (src/test/dos/runDos.sh; not part of # make test). EMULATOR=86box (default) or dosbox; SCRIPTS= a subset. test-dos: all diff --git a/docs/dvx_basic_reference.html b/docs/dvx_basic_reference.html index 58984ffc..f4728798 100644 --- a/docs/dvx_basic_reference.html +++ b/docs/dvx_basic_reference.html @@ -97,6 +97,8 @@ img { max-width: 100%; }
At the top of the Code Editor are two dropdown lists:
A WithEvents variable (Dim WithEvents acct As Account, also Private or Public, in a module, a form or a class module) is listed as an object; its events are the ones its class module declares with Event. Picking one of them creates the handler acct_EventName with the event's own parameters, so it matches what the class raises. Selecting the variable when it has no handler yet creates the handler for the class's first event, as a control's default event is created.
The editor shows one procedure at a time. Each procedure has its own buffer, and switching between them is instantaneous. The (General) section contains module-level declarations and code.
Text outside the procedure being shown is never lost: lines typed above or below the SUB move to (General), a second SUB typed into the buffer becomes a procedure of its own, and a procedure whose SUB line was deleted becomes (General) text. When you leave an event handler that was generated and never edited it is removed; empty SUBs and FUNCTIONs you wrote yourself are kept. After a save the editor shows exactly what was stored (blank lines around a procedure are normalized). A section too large for the editor opens read-only, with a message in the status bar.
Date literals are colored as numbers only when they are real dates, as the compiler reads them; the # of a file number (CLOSE #1) is not. Some words are colored like keywords without being reserved -- built-in type names, Empty, Null, EACH, EVENT, WITHEVENTS, and the console and graphics statements such as CLS, LOCATE, COLOR, SCREEN, PSET, CIRCLE, PAINT, PCOPY and PMAP. A program may use them as its own names, and after a dot (Text1.Width, Picture1.Point) they are shown as ordinary names.
Select a file in the Project Explorer and choose File > Rename File. Type the new file name; the extension may be left out (a module stays .bas, a class module .cls and a form .frm, and cannot be given another kind's extension). The file is renamed in the project's directory and the project file is saved at once with the new name, so the two always agree. Nothing changes, and a message says why, when the name is not a valid file name, when another project file or any file in the directory already has it, or when the file or the project file cannot be written -- an existing file is never overwritten. A file that has never been saved is simply given the new name, and is written under it when you save.
+A class module named after its file (it has no Attribute VB_Name line, or one that matches the file name, as File > New Class Module writes it) takes the new name as its class name: its Attribute VB_Name line is changed to match (an edit that is saved with the file, like any other). The new name must then be a valid name that no other form or class module uses. Code that uses the old class name is not changed. A form keeps its Name property; rename the form itself in the Properties window.
The Project Explorer is a tree view window listing all project files, in three groups: Forms, Modules and Classes. Double-click a file to open it: .bas and .cls files open in Code view, .frm files open in Design view. The selected file in the Project Explorer determines the target for View > Code and View > Designer commands.
A new class module starts with these header lines:
VERSION DVX 1.00 CLASS
@@ -1326,7 +1422,7 @@ Option Explicit
Name Variable name (internal mangled names like static variable placeholders are filtered out).
Type Data type: Integer, Long, Single, Double, String, Boolean, Date, Variant, Array, UDT, or the type of object. Array types show the element type (e.g., Integer()).
Value Current value. Strings are shown in quotes. Booleans as True/False. Arrays show bounds and element count (e.g., Integer(0 To 9) [10]). Uninitialized arrays show (uninitialized).
-Variants show the value they currently hold, or Empty or Null. Object variables show Nothing, or which object they refer to: the class name of a class instance, a Collection with its number of items, or the form or control.
+Variants show the value they currently hold, or Empty or Null; an OPTIONAL Variant parameter the caller left out shows Missing. Object variables show Nothing, or which object they refer to: the class name of a class instance, a Collection with its number of items, or the form or control.
The Locals window displays:
This release fixes a large number of problems in the language, the runtime, the controls and the IDE. Most programs keep working unchanged. The one change that needs attention in existing programs is variable scope: see Module Variables Need SHARED below.
+This release fixes a large number of problems in the language, the runtime, the controls and the IDE. Most programs keep working unchanged. Three changes need attention in existing programs: variable scope (see Module Variables Need SHARED), the order of the bytes in a color, and text stored in numbers (see Colors and Numbers Follow Visual Basic).
DVX BASIC now uses the QBasic scope rules. A SUB or FUNCTION sees a variable declared at module level (including the code section of a form) only when it is declared with DIM SHARED, or named in a SHARED statement inside the procedure. Before, every module variable was visible everywhere.
See: Variable Scope and SHARED
+See: Objects and Class Modules
+ +Type Size Suffix Range / Description ---- ---- ------ ------------------- + Byte 1 byte (none) 0 to 255 Integer 2 bytes % -32768 to 32767 Long 4 bytes & -2147483648 to 2147483647 Single 4 bytes ! 32-bit float, approximately 7 digits precision Double 8 bytes # 64-bit float, approximately 15 digits precision + Currency 8 bytes @ -922337203685477.5808 to 922337203685477.5807, exact to four decimal places String variable $ Variable-length, reference-counted, dynamic string Boolean 2 bytes (none) True (-1) or False (0) Date 8 bytes (none) A date and time of day from 1/1/100 to 12/31/9999 @@ -1685,13 +1820,14 @@ people.Add "Ann"Array Reference-counted multi-dimensional array (up to 8 dimensions) UDT User-defined type instance (created with TYPE...END TYPE) Object Reference to a form, a control, a class instance or a Collection; declarable as Control, Form, Object, a class name or Collection - Ref ByRef pointer to a variable slot (used for ByRef parameters) + Ref Reference to a variable, array element, TYPE field or class member variable (what a ByRef parameter holds)
Type suffixes can be appended to variable names to declare their type implicitly:
count% = 42 ' Integer
total& = 100000 ' Long
rate! = 3.14 ' Single
pi# = 3.14159265 ' Double
+price@ = 19.99 ' Currency
name$ = "Hello" ' String
Form Example Description @@ -1704,16 +1840,20 @@ name$ = "Hello" ' StringLong suffix 42&, &HFF& Force Long type Floating-point 3.14, 1.5E10, 2.5D3 Any number containing a decimal point or exponent is Double by default Single suffix 3.14! Force Single type - Double suffix 3.14# Force Double type + Double suffix 3.14# Force Double type + Currency suffix 19.99@, 5@ Force Currency type (more than four decimal places round, halves to even)
Note: Both E and D can introduce an exponent (e.g. 1.5E10 and 2.5D3 are equivalent forms of a scientific-notation double).
A literal with an Integer or Long suffix and a fraction or exponent is rounded (1E3% is 1000, 2.5% is 2). The most negative values -32768% and -2147483648& are valid. A trailing decimal point is allowed (1. is the Double 1). A numeric literal too long to read is a compile error.
When mixing types in expressions, values are automatically promoted to a common type: Integer -> Long -> Single -> Double. + joins two strings; when a string meets a number in an arithmetic operator (including +) the string is converted as VAL does ("1" + 2 is 3, "abc" + 2 is 2). Use & to join text and numbers, and VAL / STR$ to convert explicitly.
+When mixing types in expressions, values are automatically promoted to a common type: Byte -> Integer -> Long -> Single -> Double. A Currency with a Byte, Integer, Long or Currency stays Currency (see Byte and Currency below). Whole-number arithmetic keeps the type of its operands: an Integer result too large for an Integer becomes a Long (32767 + 1 is the Long 32768) and a Byte result outside 0 to 255 becomes an Integer, but a Long result too large for a Long raises error 6 (Overflow), as in Visual Basic. Convert an operand when a larger result is expected: CDBL(2147483647) + 1 is 2147483648.
++ joins two strings. When a string meets a number in an arithmetic operator (including +) or a comparison, the string must hold a number, as in Visual Basic: "1" + 2 is 3 and " 2.5 " * 2 is 5, but text that is not a number ("abc" + 2, "12abc" + 2) raises error 13 (Type mismatch). VAL reads the number at the start of any text instead (VAL("12abc") is 12). Use & to join text and numbers, and VAL / STR$ to convert explicitly.
Storing a value into a variable declared as Integer, Long, Single, Boolean or Date (by suffix, by AS clause, by DEFINT/DEFLNG/DEFSNG, or as a typed parameter, STATIC variable, FUNCTION result, TYPE field or array element) converts the value to that type. DEFtype statements also give parameters, STATIC variables and FUNCTION results their default type. Integer and Long round to the nearest whole number, with halves rounding to the nearest even number (2.5 becomes 2, 3.5 becomes 4), and raise error 6 (Overflow) when the result does not fit. Single keeps about 7 significant digits, so PRINT shows at most 7 digits of it. Boolean stores True for any non-zero number or non-empty string and False otherwise. Date takes a number as a date's number of days and reads a string as a date (error 13 when the text is not a date, error 6 outside 1/1/100 to 12/31/9999); INPUT into a Date variable reads the date the same way. A variable declared with DIM but without a type is Single (or the DEFtype for its first letter). A variable that is never declared keeps whatever type the assigned value has.
+Storing a value into a variable declared as Byte, Integer, Long, Single, Double, Currency, String, Boolean or Date (by suffix, by AS clause, by DEFtype, or as a typed parameter, STATIC variable, FUNCTION result, TYPE field or array element) converts the value to that type: a number stored in a String variable becomes its text as STR$ gives it without the leading space ("5"). Text stored in a numeric variable must be a number, as in Visual Basic: "12" and " 3.5 " convert, while text that is not a number ("abc", "12abc") raises error 13 (Type mismatch) and the variable keeps its value. DEFtype statements also give parameters, STATIC variables and FUNCTION results their default type. Byte, Integer and Long round to the nearest whole number, with halves rounding to the nearest even number (2.5 becomes 2, 3.5 becomes 4), and raise error 6 (Overflow) when the result does not fit; Currency rounds to four decimal places the same way. Single keeps about 7 significant digits, so PRINT shows at most 7 digits of it. Boolean stores True for any non-zero number or non-empty string and False otherwise. Date takes a number as a date's number of days and reads a string as a date (error 13 when the text is not a date, error 6 outside 1/1/100 to 12/31/9999); INPUT into a Date variable reads the date the same way. A variable declared with DIM but without a type is Single (or the DEFtype for its first letter). A variable that is never declared keeps whatever type the assigned value has. A variable declared with a type holds that type from the start, before anything is stored in it: VARTYPE of a Long that was never assigned is 3 (vbLong) and of a String 8 (vbString).
Dim n As Integer
n = 3.7 ' n is 4
n = 40000 ' Error 6: Overflow
+n = "12" ' n is 12
+n = "twelve" ' Error 13: Type mismatch (n is still 12)
Dim b As Boolean
b = 3 ' b is True
Dim s
@@ -1721,6 +1861,17 @@ s = 1 / 3 ' s is Single: prints 0.3333333
x = 1 / 3 ' x was never declared and keeps the Double: prints 0.333333333333333
Boolean values use -1 for True and 0 for False. Any non-zero numeric value is treated as True in a conditional context. The keywords True and False are reserved and may be used anywhere a Boolean value is expected.
+A Byte holds a whole number from 0 to 255 in one byte. Storing a value outside that range raises error 6 (Overflow). Arithmetic on two Bytes gives a Byte when the result is 0 to 255 and an Integer otherwise; negating a Byte gives an Integer. CBYTE converts a value to a Byte.
+A Currency holds an amount of money exactly: it counts ten-thousandths, so it has four decimal places and adding, subtracting or multiplying amounts never drifts (adding 0.1@ ten times gives exactly 1). A value with more decimal places is rounded to four, halves to the nearest even digit (CCUR(2.00005) is 2, CCUR(2.00015) is 2.0002), and a result outside the Currency range raises error 6 (Overflow). +, - and * on a Currency and a Byte, Integer, Long or Currency give a Currency; with a Single or Double they give a Double, and / always gives a Double. Comparisons of Currency values are exact. PRINT and STR$ show a Currency with only the decimals it needs (12.5); FORMAT$ lays it out like any other number. A FOR loop with a Currency counter steps exactly. CCUR converts a value to a Currency.
+Dim total As Currency
+Dim i As Integer
+For i = 1 To 3
+ total = total + 19.99@
+Next
+Print total ' 59.97
+Print FORMAT$(total, "0.00")
+GET and PUT store a Byte in 1 byte and a Currency in 8 (see GET / PUT).
A Date holds a calendar date and a time of day, as in Visual Basic: the whole part of its number counts days from 12/30/1899 and the fraction is the time (0.5 is noon, so 36526.5 is noon on 1/1/2000). Days before 12/30/1899 are negative numbers whose time still counts forward from midnight: -1.25 is 6:00 AM on 12/29/1899. CDBL gives a date's number and CDATE turns a number back into a date.
Write a date literal between # signs. The month comes before the day; a year can also come first, and month names (full or abbreviated) work too. A time can follow the date or stand alone:
@@ -1755,7 +1906,12 @@ Print TypeName(v) ' Date v = Null Print IsNull(v) ' TrueDeclare a Variant with AS VARIANT. Arrays of Variants (DIM list(10) AS VARIANT), Variant parameters, and FUNCTIONs and properties AS VARIANT work the same way; each element of a Variant array starts Empty. A variable declared with DIM but no AS clause, or never declared at all, is not a Variant: it keeps the older rules described under Assignment Conversion.
-Storing a Variant in a variable of another type converts the value to that type, as for any other assignment. A Variant that is given an array refers to that same array: a change made through either name is seen through the other, as when one array variable is assigned to another. Index it like the array itself, v(1).
+Storing a Variant in a variable of another type converts the value to that type, as for any other assignment. A Variant that is given an array gets its own copy of the array, as in Visual Basic: after v = list, changing list(1) does not change v(1), and w = v copies again. The same holds when a Variant holding an array is passed BYVAL, stored in an object's member or added to a Collection. Index it like the array itself, v(1).
+Dim list(2) As Integer, v As Variant
+list(0) = 1
+v = list ' v holds a copy
+list(0) = 5
+Print v(0); list(0) ' 1 5
Empty means that nothing has been stored in the Variant yet. A new Variant is Empty, and assigning Empty makes it Empty again. In arithmetic Empty counts as 0 and in text as "", so a counter kept in a Variant needs no starting value; PRINT shows nothing for it.
Dim total As Variant
@@ -1768,13 +1924,15 @@ If IsEmpty(v) Then Print "Nothing stored yet"
Null + 1, Null * x, -Null, Not Null Null
Null = 1, Null < x, Null = Null Null (a comparison with Null is never True)
Abs(Null), Int(Null), Fix(Null), Sgn(Null) Null
+ Left(Null, 1), UCase(Null), Len(Null), ... Null (the Variant string functions; see below)
"Name: " & Null "Name: " (& treats Null as "")
Null & Null Null
False And Null False
True Or Null True
False Imp Null, Null Imp True True
Any other And, Or, Xor, Eqv or Imp with Null Null
-An IF, ELSEIF, WHILE, DO or LOOP condition that is Null counts as False. Any other use that needs a real number -- storing Null in a numeric, Boolean or Date variable, CINT(Null) and the other conversion functions, a string function such as LEN or UCASE$, or an array subscript -- raises error 94 (Invalid use of Null).
+An IF, ELSEIF, WHILE, DO or LOOP condition that is Null counts as False. Any other use that needs a real number -- storing Null in a numeric, Boolean or Date variable, CINT(Null) and the other conversion functions, a string function ending in $ such as UCASE$ or LEFT$, or an array subscript -- raises error 94 (Invalid use of Null).
+As in Visual Basic, the string functions without $ are Variant functions that pass Null through: LEFT, RIGHT, MID, UCASE, LCASE, TRIM, LTRIM, RTRIM, CHR, HEX, OCT, STR, SPACE and STRING give Null when their text (or number) argument is Null, and so do LEN, INSTR, INSTRREV, STRCOMP, LIKE and the date parts YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, WEEKDAY and DATEPART. Their $ forms (LEFT$, UCASE$ ...) always give text and raise error 94 for Null.
Because Null = Null is itself Null, test for Null with ISNULL, never with =:
If IsNull(price) Then
Print "No price yet"
@@ -1786,14 +1944,22 @@ End If
Function Returns Description
-------- ------- -----------
VARTYPE(v) Integer A number for the kind of value v holds (see VarType Constants): vbEmpty (0), vbNull (1), vbInteger (2), vbString (8), vbObject (9) and so on. For an array it is vbArray (8192) plus the element type, so an Integer array is 8194 and a Variant array 8204.
- TYPENAME(v) String The name of the kind of value: "Integer", "Long", "Single", "Double", "String", "Boolean", "Date", "Empty", "Null", the name of a TYPE, "Integer()" for an array, and for objects the class name, "Collection", "Nothing", or the type of a form or control.
+ TYPENAME(v) String The name of the kind of value: "Byte", "Integer", "Long", "Single", "Double", "Currency", "String", "Boolean", "Date", "Empty", "Null", "Error" (a left-out OPTIONAL argument), the name of a TYPE, "Integer()" for an array, and for objects the class name, "Collection", "Nothing", or the type of a form or control.
ISEMPTY(v) Boolean True when v is Empty.
ISNULL(v) Boolean True when v is Null.
ISNUMERIC(v) Boolean True when v is a number, True or False, Empty, or text that reads as a number ("12", "1.5"). False for Null, dates and other text.
ISOBJECT(v) Boolean True when v is an object reference, including Nothing.
ISARRAY(v) Boolean True when v is an array.
+ ISMISSING(v) Boolean True when v is an OPTIONAL Variant parameter the caller left out (see Procedures).
ISDATE(v) Boolean True for a Date or text holding a date (see Date and Time Functions).
- CVAR(v) Variant v unchanged, as a Variant.
+ CVAR(v) Variant v unchanged, as a Variant.
+ ARRAY(a, ...) Variant A Variant holding an array of copies of the values, numbered from OPTION BASE (0 unless OPTION BASE 1); ARRAY() is empty (UBOUND -1).
+Dim days As Variant
+days = Array("Mon", "Tue", "Wed")
+Print days(1) ' Tue
+For Each d In Array(1, 2, 3)
+ Print d;
+Next
Sub Describe(ByVal v As Variant)
Select Case VarType(v)
Case vbEmpty
@@ -1821,7 +1987,7 @@ End Sub
5 MOD Modulus
6 + - Addition, subtraction (+ also concatenates strings)
7 & String concatenation
- 8 = <> < > <= >= Is Comparison (returns Boolean)
+ 8 = <> < > <= >= Is Like Comparison (returns Boolean)
9 NOT Logical/bitwise NOT
10 AND Logical/bitwise AND
11 XOR Logical/bitwise exclusive-or
@@ -1848,9 +2014,14 @@ End Sub
> Greater than
<= Less than or equal to
>= Greater than or equal to
- Is Both sides refer to the same object (objects only)
+ Is Both sides refer to the same object (objects only)
+ Like The text on the left matches the pattern on the right
String comparisons are controlled by OPTION COMPARE. With OPTION COMPARE BINARY (the default) strings are compared by byte value. With OPTION COMPARE TEXT comparisons are case-insensitive. A comparison with Null gives Null (see Variants).
Is compares object references, not values: a Is b is True when both refer to the very same object, and x Is Nothing tests for an object variable that holds no object. Either side may be a form or control name. Using Is on something that is not an object raises error 424 (Object required).
+text Like pattern is True when the whole of text matches the pattern. In the pattern, ? stands for any one character, * for any run of characters (none too), # for one digit 0 to 9, [list] for one character in the list and [!list] for one character not in it; anything else stands for itself. A list holds characters and ranges such as a-z ([A-Za-z0-9_]); to match a literal ?, *, # or [, put it in brackets ([*]). OPTION COMPARE TEXT makes the match ignore case. Like with Null gives Null, and a pattern with a [ that has no ] raises error 93.
+If name$ Like "A*" Then Print "Starts with A"
+If code$ Like "[A-Z]##" Then Print "A letter and two digits"
+If file$ Like "*.[!b]*" Then Print "Not a .b... extension"
TypeOf object Is TypeName is True when the object is of that type. TypeName can be a class module's name, Collection, Form, Control (any control), Object (any object that is not Nothing), or a control type such as TextBox. It is False for Nothing. Use Not TypeOf ... Is ... for the opposite test.
If player Is Nothing Then Set player = New Player
If a Is b Then Print "Same object"
@@ -1949,6 +2120,15 @@ END SUB
DIM SHARED and REDIM SHARED are only allowed at module level. Inside a procedure use the SHARED statement:
SHARED variable [()] [AS type] [, variable [()] [AS type] ...]
SHARED gives the procedure access to the named module variables (write name() for an array). The AS type, when given, must match the module variable's declaration. Naming a variable that does not exist at module level creates it there (in a form's code, as a variable of that form).
+Every name in a program is looked up the same way, in this order:
+A SUB or FUNCTION can be called before its definition, and a TYPE, ENUM or class -- with its member variables and procedures -- can be used before it is declared: the compiler reads every declaration first. A name after a dot is a member of the object before it: when that object's class is known (see Objects and Class Modules) the member is looked up in the class when the program is compiled, else when it runs.
At module level, PUBLIC and PRIVATE can be used in place of DIM, as in Visual Basic. PUBLIC declares a variable that every SUB and FUNCTION can use (the same as DIM SHARED); PRIVATE declares one that only module-level code sees (the same as DIM).
Public highScore As Long ' same as Dim Shared highScore As Long
@@ -1988,6 +2168,9 @@ Const BIG = MAX_SIZE > 50
Anything else -- a variable, a FUNCTION of your own, or a built-in whose answer can change from run to run such as TIMER, NOW, RND or DIR$ -- is a compile error ("Constant expression required"). An expression that fails, such as a division by zero or a result too big for its type, is also reported when the program is compiled. Text comparisons in a constant follow the OPTION COMPARE setting in effect where the constant is declared. A string constant can be at most 255 characters long.
Without an AS clause the constant takes the type of its value: a whole number makes a Long constant, a number with a fraction a Double constant, and strings, Booleans and dates keep their type. With an AS clause the value is converted to that type: Integer and Long values are rounded and must fit the type, and a string for a numeric type (or a number for String) is a compile error.
Constants declared at module level are visible to all procedures. A CONST declared inside a SUB or FUNCTION is local to that procedure; it may reuse the name of a module constant without changing it.
+PUBLIC CONST and PRIVATE CONST work as in Visual Basic. A Private constant, and any constant of a form or class module, belongs to the file it is declared in: another module, form or class of the project can declare its own constant of the same name, and does not see this one. A Public constant, or a plain CONST in a module, is seen by the whole program. A class module or form cannot have a Public constant ("Constants are not allowed as Public members of a class module or form").
+Public Const APP_VERSION = "2.1" ' every file of the project
+Private Const BUFFER_SIZE = 512 ' this file only
Defines a user-defined type (record/structure).
TYPE TypeName
@@ -2030,7 +2213,7 @@ Dim heading As Direction
heading = West
If heading = Direction.West Then Print "Going west"
The first member is 0 and each member without a value is one more than the member before it. A value may be any constant expression (see CONST), including members declared earlier in the same ENUM or in another one. Members are Long constants and can be used on their own (West) or with the ENUM name in front (Direction.West). If two ENUMs have a member with the same name, that name must be written with its ENUM name.
-The ENUM name can be used as a type wherever a type is allowed: DIM, parameters, FUNCTION results and TYPE fields. Such a variable is a Long, so it can hold any whole number, not only the members. ENUMs are declared at module level (not inside a SUB or FUNCTION) and are visible everywhere in the program; declare an ENUM before the code that uses its members.
+The ENUM name can be used as a type wherever a type is allowed: DIM, parameters, FUNCTION results and TYPE fields. Such a variable is a Long, so it can hold any whole number, not only the members. ENUMs are declared at module level (not inside a SUB or FUNCTION) and are visible everywhere in the program; declare an ENUM before the code that uses its members. PRIVATE ENUM keeps the ENUM and its members to the file it is declared in, like PRIVATE CONST; PUBLIC ENUM (the same as a plain ENUM) is seen everywhere. Members are constants: Direction.West = 3 is a compile error ("Cannot assign to a constant").
Forward-declares a SUB or FUNCTION. This is rarely required because the compiler supports forward references within a module, but it is still accepted for compatibility:
DECLARE SUB name ([BYVAL] [OPTIONAL] param AS type, ...)
@@ -2046,7 +2229,7 @@ END DECLARE
Declare Function SQLOpen(ByVal path As String) As Long
Declare Sub SQLClose(ByVal db As Long)
End Declare
-Arguments are always passed by value. Each argument is converted to the type its parameter is declared with before the call, so a Double passed to a Long parameter arrives as a whole number and a Single parameter receives a Single. Declare Integer only for native values that fit in 16 bits; use Long for 32-bit values, or large results are cut down to 16 bits.
+Arguments are always passed by value. Each argument is converted to the type its parameter is declared with before the call, so a Double passed to a Long parameter arrives as a whole number and a Single parameter receives a Single. Declare Integer only for native values that fit in 16 bits; use Long for 32-bit values, or large results are cut down to 16 bits. A Byte parameter or result is 8 bits. A Currency parameter receives the amount as a 64-bit whole number of ten-thousandths; a DECLARE FUNCTION cannot return a Currency.
See also: BASIC Runtime Libraries
Declares a local variable that retains its value between calls.
@@ -2056,7 +2239,21 @@ End Declare count = count + 1 Print count End Sub -In a class module, a STATIC variable is shared by every object of the class; use a member variable for a value each object keeps separately.
+Several variables can be declared in one STATIC statement, separated by commas, each with its own AS clause; STRING * n works as in DIM. Arrays and TYPE variables can be STATIC too and keep their contents between calls: a fixed-size array (Static hits(1 To 10) As Long) or a dynamic one sized later with REDIM (Static names() As String). In a class module each object has its own copy of a procedure's STATIC variables, arrays and TYPE variables, as in Visual Basic: two objects counting calls count separately. A STATIC AS NEW object or WithEvents variable is not supported (a compile error); use a module-level or member variable for those.
+Sub Remember(ByVal word As String)
+ Static seen(1 To 3) As String
+ Static count As Integer
+ count = count + 1
+ seen((count - 1) Mod 3 + 1) = word
+ Print seen(1); " "; seen(2); " "; seen(3)
+End Sub
+Writing STATIC after a SUB or FUNCTION header makes every local variable of the procedure static, the ones declared with DIM and the ones used without a declaration (QBasic and Visual Basic):
+Sub Tally(points As Integer) Static
+ total = total + points ' keeps its value between calls
+ Dim calls As Integer ' a STATIC variable too
+ calls = calls + 1
+ Print calls; total
+End Sub
Sets compiler options. Must appear before any executable code.
OPTION BASE 0 ' Arrays start at index 0 (default)
@@ -2070,7 +2267,9 @@ OPTION EXPLICIT ' All variables must be declared with DIM
DEFLNG letterRange
DEFSNG letterRange
DEFDBL letterRange
-DEFSTR letterRange
+DEFSTR letterRange
+DEFBYTE letterRange
+DEFCUR letterRange
DefInt I-N ' Variables starting with I through N default to Integer
DefStr S ' Variables starting with S default to String
The LET keyword is optional and supported for compatibility.
+Assigning a whole TYPE value to a TYPE variable, array element or field copies the value's fields into the record that is already there, as in Visual Basic: the two stay separate records (changing one later does not change the other), and a SUB that was passed one of the target's fields by reference sees the new value.
Exchanges the values of two variables or array elements. The two operands should be the same type.
SWAP variable1, variable2
@@ -2095,6 +2295,17 @@ field = Space$(8)
LSet field = "Name" ' "Name "
RSet field = "42" ' " 42"
The target may be an array element or a TYPE field. It must hold a string; any other value raises error 13 (Type mismatch).
+LSET also copies a whole record: when the target is a TYPE variable, the value must be another TYPE variable, and its bytes -- laid out as PUT writes the record to a file -- are copied into the target, even when the two are of different TYPEs. When the source is shorter the rest of the target is filled with spaces; when it is longer the extra bytes are dropped. This is meant for records of numbers and fixed-length strings (STRING * n), as used with random-access files; a variable-length string field does not copy meaningfully between different TYPEs. RSET cannot copy records.
+Type Header
+ kind As String * 2
+ size As Integer
+End Type
+Type Raw
+ bytes As String * 4
+End Type
+Dim h As Header, r As Raw
+r.bytes = "AB" + Chr$(10) + Chr$(0)
+LSet h = r ' h.kind = "AB", h.size = 10
Stores an object reference -- a form, a control, an instance of a class module, a Collection, or Nothing -- in a variable, array element, TYPE field or property. After SET both names refer to the same object; nothing is copied.
SET variable = objectExpression
@@ -2114,7 +2325,9 @@ Set list = Nothing ' let it go
The value must be an object or Nothing; anything else raises error 424 (Object required). SET is not used for ordinary numeric or string assignment.
Frees the memory of a dynamic array (declared with DIM name(), created by REDIM, or an array parameter) and resets it to the undimensioned state; using it again before a REDIM raises error 13. A fixed array (declared with bounds) keeps its bounds and has every element reset to zero, an empty string, False or a fresh TYPE instance.
-ERASE arrayName
+ERASE arrayName
+ERASE array1, array2, ...
+One ERASE can name several arrays, separated by commas.
Defines a subroutine (no return value).
-SUB name ([BYVAL | BYREF] [OPTIONAL] param AS type, ...)
+SUB name ([BYVAL | BYREF] [OPTIONAL] param AS type, ...) [STATIC]
SUB name (arrayParam() AS type, ...)
statements
END SUB
Sub Greet(ByVal name As String)
Print "Hello, " & name
End Sub
-Parameters are passed by reference by default (nested calls pass the same variable on, so a SUB called from a SUB can still change the original); BYREF may be written out to say so, and BYVAL selects value semantics. Writes to a by-reference parameter update the caller's variable, including individual array elements: `bump a(3)` passed to a by-reference parameter will modify `a(3)` in place. A parameter written as name() receives a whole array by reference; the caller passes `arr()` (or just `arr`), and the SUB may index, REDIM or ERASE it. Use EXIT SUB to return early. A SUB is called either with or without parentheses; when used as a statement, parentheses are optional:
+Parameters are passed by reference by default (nested calls pass the same variable on, so a SUB called from a SUB can still change the original); BYREF may be written out to say so, and BYVAL selects value semantics. A by-reference parameter is the caller's variable itself, as in Visual Basic: a write to it changes the variable at once (the caller's code, or another parameter naming the same variable, sees the new value straight away, even if the procedure later stops with an error), and what is stored is converted to the variable's own type (a STRING * n keeps its length). This holds for a plain variable, an array element (`bump a(3)` changes `a(3)` in place), a TYPE field (`bump rec.count`, `bump recs(2).count`, `bump rec.inner.count`) and, inside a class, one of the class's own member variables. While an array element (or a field of one) is passed by reference, its array cannot be given a new size or erased: REDIM of that array, or ERASE of it when it is a dynamic array, raises error 10 (This array is fixed or temporarily locked), as in Visual Basic (ERASE of a fixed-size array only clears its elements and is allowed). The array is free again as soon as the call returns. Assigning a whole TYPE value to the variable (or element) whose field is being passed copies the value into the same record, so the parameter sees the new value (as in Visual Basic). A parameter written as name() receives a whole array by reference; the caller passes `arr()` (or just `arr`), and the SUB may index, REDIM or ERASE it. Use EXIT SUB to return early. A SUB is called either with or without parentheses; when used as a statement, parentheses are optional:
Greet "World"
Greet("World")
Call Greet("World")
+As in Visual Basic and QBasic, a single argument in parentheses after the name of a SUB called without CALL (Bump (n), or Bump(n)) is an expression in parentheses, so it is passed by value: the SUB gets a copy and n is not changed. Write Bump n, or Call Bump(n), to pass n by reference.
+An argument is converted to the type of a typed parameter (one declared with AS, a type suffix or a DEFtype rule). A value -- a literal, an expression, a variable in its own parentheses, or anything given to a BYVAL parameter -- is converted the way an assignment would convert it (Report 3.7 gives an Integer parameter 4). A variable, array element or TYPE field given to a by-reference parameter must have the parameter's own type, as in Visual Basic: an Integer parameter cannot be the caller's Double variable ("ByRef argument type mismatch" when the program is compiled); pass CINT(d), or (d) in its own parentheses, to pass a converted copy. A variable declared without a type (a Variant) holds any value and is passed as it is. Through an object variable declared As Object the procedure is only found when the program runs, so a variable of another type is passed as a converted copy instead.
Sub Fill(v() As Integer, ByRef count As Integer)
ReDim v(9) As Integer
v(9) = 7
@@ -2260,7 +2475,7 @@ Dim a(3) As Integer
Dim n As Integer
Fill a(), n
Optional Parameters
-Mark a parameter OPTIONAL to allow callers to omit it. An optional parameter must be positioned after all required parameters and receives an empty/zero default when not supplied.
+Mark a parameter OPTIONAL to allow callers to omit it. An optional parameter must be positioned after all required parameters. A caller can leave out the optional parameters at the end, or any optional parameter in the middle by writing nothing between the commas: Report a, , c. A left-out parameter of a type receives that type's empty value (0, "", False, a date of 0, Nothing); a left-out Variant parameter is Missing, which ISMISSING detects. Leaving out a parameter that is not OPTIONAL is a compile error (Argument not optional).
Sub Announce(ByVal msg As String, Optional ByVal loud As Integer)
If loud Then
Print UCase$(msg)
@@ -2271,6 +2486,39 @@ End Sub
Announce "hello" ' loud defaults to 0
Announce "hello", True ' loud is -1
+An OPTIONAL parameter can also name the value it takes when it is left out, after an equals sign: Optional count As Integer = 10. The default is a constant expression, like the value of a CONST: numbers, strings, dates, other constants and the built-in functions CONST allows, converted to the parameter's type (a value that does not fit is a compile error). A parameter with a default is never Missing -- ISMISSING is False for it -- whether it is left out at the end, skipped between commas, or the procedure is reached through an object. Defaults work for SUB and FUNCTION procedures and for the procedures and Property procedures of class modules, including calls written before the procedure itself. As in Visual Basic the default is the one the declaration being called gives: a call through an interface (a variable declared As the interface class) passes the interface's default, even when the implementing class declares another. An object parameter (As Object, As Control, As Form or As a class) can only have the default Nothing, as in Visual Basic: Optional target As Object = Nothing; a Variant parameter may have Nothing too. A default cannot be given to a required parameter, an array or a TYPE, or in DEF FN and DECLARE LIBRARY. A DECLARE SUB / FUNCTION line must say whether each parameter has a default, as its definition does.
+Const SIDE = 10
+
+Function Area(Optional w As Integer = SIDE, Optional h As Integer = SIDE * 2) As Long
+ Area = w * h
+End Function
+
+Print Area() ' 200
+Print Area(2) ' 40
+Print Area(, 3) ' 30
+ISMISSING works as in Visual Basic: only an OPTIONAL Variant parameter without a default can be Missing, and a Missing value passed on to another procedure's OPTIONAL Variant parameter is still Missing there. ISMISSING of anything else is False. Using a Missing value in any other way -- in arithmetic, in text, or storing it in a typed variable -- raises error 448.
+Sub Greet(ByVal name As String, Optional title As Variant)
+ If IsMissing(title) Then
+ Print "Hello, "; name
+ Else
+ Print "Hello, "; title; " "; name
+ End If
+End Sub
+
+Greet "Ada" ' Hello, Ada
+Greet "Ada", "Countess" ' Hello, Countess Ada
+Named Arguments
+An argument can be given by the name of its parameter, followed by := and the value, as in Visual Basic. Named arguments come after any positional ones, in any order; the parameters left out must be OPTIONAL.
+Sub Report(ByVal title As String, Optional ByVal width As Integer = 40, Optional ByVal footer As String = "")
+ Print title; width; footer
+End Sub
+
+Report "Sales", footer:="(end)" ' width is 40
+Report width:=60, title:="Stock"
+Print Mid$(String:="hello", Start:=2) ' ello
+MsgBox Prompt:="Saved", Title:="Notes"
+Named arguments work for SUB and FUNCTION procedures, DEF FN functions, DECLARE LIBRARY procedures and the procedures of class modules (through a variable of the class, or through an Object or Variant variable), and for the built-in functions that Visual Basic gives argument names to: MsgBox (Prompt, Buttons, Title), InputBox (Prompt, Title, Default), Mid (String, Start, Length), Left and Right (String, Length), InStr (Start, String1, String2), InStrRev (StringCheck, StringMatch, Start, Compare), Replace (Expression, Find, Replace, Start, Count, Compare), Split (Expression, Delimiter, Limit, Compare), Join (SourceArray, Delimiter), Filter (SourceArray, Match, Include, Compare), StrComp (String1, String2, Compare), String (Number, Character), Format (Expression, Format), IIf (Expression, TruePart, FalsePart), DateAdd (Interval, Number, Date), DateDiff and DatePart (Interval, Date1 and Date2 or Date, FirstDayOfWeek, FirstWeekOfYear), DateSerial (Year, Month, Day), TimeSerial (Hour, Minute, Second), Weekday (Date, FirstDayOfWeek), WeekdayName (Weekday, Abbreviate, FirstDayOfWeek), MonthName (Month, Abbreviate), and a Collection's Add (Item, Key, Before, After), Item and Remove (Index).
+A name the procedure does not have is a compile error (Named argument not found), and so is a parameter given twice. When the procedure is only found as the program runs (a method called through an Object or Variant variable) the same mistakes raise error 448 (Named argument not found) and error 450 (Wrong number of arguments). The methods of forms and controls do not take named arguments (error 446, Object doesn't support named arguments), and neither does a ParamArray parameter.
ParamArray
The last parameter of a SUB or FUNCTION can be marked PARAMARRAY to accept any number of extra arguments. It is written as an array, name(), and receives every remaining argument as an array numbered from 0. With no extra arguments the array is empty: UBOUND returns -1.
Sub PrintAll(ByVal title As String, ParamArray items())
@@ -2293,13 +2541,14 @@ Function Sum(ParamArray n() As Double) As Double
Next
Sum = total
End Function
-Without an AS clause (and no type suffix or DEFtype rule for the name) the ParamArray is an array of Variants (TYPENAME gives "Variant()"): each element holds the argument that was passed with its own type, so numbers, strings and objects can be mixed, and FOR EACH can walk it. With an AS clause every argument is converted to that type. Arguments are passed by value: changing an element does not change the caller's variable. A PARAMARRAY cannot be combined with OPTIONAL parameters, cannot be BYVAL or BYREF, cannot hold a TYPE, and takes at most 31 arguments. It is not available in DEF FN or DECLARE LIBRARY.
+Without an AS clause (and no type suffix or DEFtype rule for the name) the ParamArray is an array of Variants (TYPENAME gives "Variant()"): each element holds the argument that was passed with its own type, so numbers, strings and objects can be mixed, and FOR EACH can walk it. With an AS clause every argument is converted to that type. An argument that is a variable, a whole array, an array element (a(i), m(i, j)), a TYPE field (rec.f, arr(i).f, rec.inner.f) or, inside a class, one of the class's own member variables is passed by reference, as in Visual Basic: the element of the ParamArray is that variable, so a write to it changes the variable at once, converted to the variable's type (a value that cannot be converted raises its error at the assignment), and passing the element on (Bump v(0)) passes the variable. With an AS clause an argument is passed by reference only when its own type is that type; others are converted and passed by value. The same holds for a class's methods, whether the object variable is declared as the class or As Object / As Variant. While the call runs, the arrays whose elements were given are locked: REDIM of one of them (or ERASE of a dynamic one) raises error 10, as for any array element passed by reference. Copying the ParamArray (keep = v) copies the values, not the variables. Expressions and object properties are passed by value. Leaving an argument out (Sum 1, , 3) gives a Missing element. A PARAMARRAY cannot be combined with OPTIONAL parameters, cannot be BYVAL or BYREF, cannot hold a TYPE, and takes at most 31 arguments. It is not available in DEF FN or DECLARE LIBRARY.
FUNCTION...END FUNCTION
Defines a function with a return value.
-FUNCTION name ([BYVAL] [OPTIONAL] param AS type, ...) AS returnType
+FUNCTION name ([BYVAL] [OPTIONAL] param AS type, ...) AS returnType [STATIC]
statements
name = returnValue
END FUNCTION
+STATIC after the header keeps every local variable of the SUB or FUNCTION between calls (see STATIC).
Function Square(ByVal n As Double) As Double
Square = n * n
End Function
@@ -2341,7 +2590,7 @@ CALL name(args)
GOTO label
GOSUB label
RETURN
-A label is a name followed by a colon at the start of a line. Labels belong to the SUB or FUNCTION they are in (module-level labels to the module level): a jump can only reach a label of the same procedure, the same label name can be used in several procedures, and a label may have the same name as a variable. A jump to a label that is not defined in the procedure, or to a SUB or variable name, is a compile error.
+A label is a name followed by a colon at the start of a line, or a line number at the start of a line (see Line Numbers below). Labels belong to the SUB or FUNCTION they are in (module-level labels to the module level): a jump can only reach a label of the same procedure, the same label name can be used in several procedures, and a label may have the same name as a variable. A jump to a label that is not defined in the procedure, or to a SUB or variable name, is a compile error.
GOSUB records where it was called from, runs the code at the label, and RETURN goes back to the statement after the GOSUB. This works at module level and inside a SUB or FUNCTION alike. Each procedure call has its own set of pending GOSUBs: RETURN only returns from a GOSUB made in the same procedure call, and leaving the procedure (END SUB, EXIT SUB, EXIT FUNCTION or an error handled in a caller) discards the ones still pending. RETURN with no pending GOSUB raises error 3 (RETURN without GOSUB). To leave a SUB or FUNCTION, use EXIT SUB / EXIT FUNCTION.
GoSub Initialize
Print "Done"
@@ -2364,6 +2613,14 @@ End Sub
ON expression GOTO label1, label2, ...
ON expression GOSUB label1, label2, ...
If the expression evaluates to 1, control goes to the first label; 2, the second; and so on. If out of range, execution falls through. The labels follow the same rules as GOTO and GOSUB (same procedure only).
+Line Numbers
+As in QBasic, a line can start with a line number, a whole number from 0 to 65529. A line number is a label: GOTO, GOSUB, ON ... GOTO / GOSUB, ON ERROR GOTO, RESUME and RESTORE can name it, it belongs to its procedure like any other label, and the same number cannot start two lines of a procedure. Numbers need not be in order, lines without a number are fine, and the number is read as a value, so 010 and 10 name the same line. IF can branch to a line number directly: IF x THEN 100 ELSE 200 is IF x THEN GOTO 100 ELSE GOTO 200, and IF x GOTO 100 works too.
+10 INPUT "Guess"; g
+20 IF g = 7 THEN 50
+30 PRINT "No"
+40 GOTO 10
+50 PRINT "Yes"
+In a program with line numbers, ERL gives the number of the last numbered line at or before the statement where the error happened (0 when there is none); in a program without them it gives the source line.
Input "Enter your name: "; name$
Input #1, line$
-The target may be a variable, an array element or a TYPE field (INPUT, INPUT #, LINE INPUT # and READ alike).
+The target may be a variable, an array element or a TYPE field (INPUT, INPUT #, LINE INPUT # and READ alike). INPUT and INPUT # read a number into a numeric target the way VAL does (12abc gives 12).
Inline data pool for constants.
DATA value1, value2, "string", ...
READ variable1, variable2, ...
-RESTORE
-DATA statements define a pool of values. READ reads the next value from the pool into a variable, array element or TYPE field. RESTORE resets the read pointer to the beginning. An item in quotes is a string; an unquoted item is taken as raw text up to the next comma, colon, comment or end of line (so keywords, several words and text such as 12abc are read as they are written) and converted when READ into a numeric variable. The most negative Integer and Long values (-32768, -2147483648) are accepted.
+RESTORE [label] +DATA statements define a pool of values. READ reads the next value from the pool into a variable, array element or TYPE field. RESTORE resets the read pointer to the beginning; RESTORE label (or a line number) makes the next READ take the first DATA item at or after that label. An item in quotes is a string; an unquoted item is taken as raw text up to the next comma, colon, comment or end of line (so keywords, several words and text such as 12abc are read as they are written) and converted when READ into a numeric variable, which raises error 13 (Type mismatch) when the item is not a number. The most negative Integer and Long values (-32768, -2147483648) are accepted.
Data 10, 20, 30, "Hello"
Read a, b, c, msg$
Print a; b; c; msg$
@@ -2448,10 +2705,12 @@ Restore
ON ERROR GOTO label ' Enable error handler
ON ERROR GOTO 0 ' Disable error handler
+ON ERROR RESUME NEXT ' Go on at the next statement after any error
RESUME ' Retry the statement that caused the error
RESUME NEXT ' Continue at the next statement after the error
+RESUME label ' Continue at a label or line number (RESUME 0 is RESUME)
ERROR n ' Raise a runtime error with error number n
-The ERR keyword returns the current error number (a Long) in expressions and ERL the source line the error was raised on (both are 0 when no error is active, and both reset after RESUME). RESUME or RESUME NEXT executed while no error is active raises error 20 (RESUME without error). ERROR n raises error n with its standard message (ERROR 11 reports "Division by zero"); numbers without a standard message report "User-defined error".
+The ERR keyword returns the current error number (a Long) in expressions and ERL the source line the error was raised on, or in a program with line numbers the last line number at or before it (both are 0 when no error is active, and both reset after RESUME). RESUME or RESUME NEXT executed while no error is active raises error 20 (RESUME without error). ERROR n raises error n with its standard message (ERROR 11 reports "Division by zero"); numbers without a standard message report "User-defined error".
Each SUB and FUNCTION has its own ON ERROR handler:
ON ERROR RESUME NEXT, as in Visual Basic, needs no handler: a statement that fails is abandoned and the program goes on at the next one, with ERR (Err.Number) and Err.Description telling what went wrong. An error in a procedure that has no handler of its own leaves that procedure, and the program goes on after the statement that called it. The statement clears ERR, and so does the end of the procedure that used it; check ERR right after the statements that may fail:
+On Error Resume Next
+Kill "old.tmp"
+If Err.Number <> 0 Then Print "Not deleted: "; Err.Description
+Err.Clear
+On Error GoTo 0
+Err holds the current error the way ERR and ERROR do:
+ Member Meaning
+ ------ -------
+ Err or Err.Number The error number (0 when there is none); Err.Number = n sets it without raising it
+ Err.Description The error's message ("" when there is none)
+ Err.Source The source text the last Err.Raise gave ("" otherwise)
+ Err.Raise number [, source [, desc]] Raises error number (1 to 65535) with that source and description; without a description the number's standard message
+ Err.Clear Clears the error: ERR becomes 0, the description and source ""
+ERROR$(n) (or ERROR(n)) gives the standard message of error number n, "User-defined error" for a number without one and "" for 0; without an argument, the current error's message.
+On Error GoTo Failed
+Err.Raise 1001, "Loader", "The file has no header"
+Exit Sub
+Failed:
+ Print Err.Number; Err.Description; " ("; Err.Source; ")"
+ Resume Next
RESUME NEXT continues after the failing statement. When the error happened in the condition of an IF, ELSEIF, WHILE, DO or LOOP, the condition counts as false (the block is skipped, the loop is left); after an error in a FOR statement the loop is skipped; after an error in the SELECT CASE expression the whole SELECT CASE block is skipped; after an error in a CASE test that test counts as false (the next value or CASE is tried); after an error in an ON ... GOTO/GOSUB selector no branch is taken and execution continues with the next statement.
On Error GoTo ErrorHandler
Open "missing.txt" For Input As #1
@@ -2478,8 +2759,9 @@ ErrorHandler:
6 Overflow (value does not fit the target type; EXP, ^ or other math leaving the numeric range)
7 Out of memory
9 Subscript out of range / invalid variable or field index
+ 10 This array is fixed or temporarily locked (REDIM, or ERASE of a dynamic array, while one of its elements, or a field of one, is passed by reference to a procedure that is still running)
11 Division by zero
- 13 Type mismatch / not an array / not a TYPE instance
+ 13 Type mismatch (text that is not a number where a number is needed) / not an array / not a TYPE instance
14 Out of string space (no memory to build a string)
20 RESUME without error
26 FOR loop nesting too deep
@@ -2492,17 +2774,23 @@ ErrorHandler:
58 File already exists or rename failed
59 Bad record length (OPEN ... LEN must be 1 to 32767; GET/PUT data longer than LEN)
63 Bad record number (SEEK, GET or PUT position below 1)
+ 64 Bad file name (WIDTH of a device DVX does not have)
67 Too many files open
68 Device unavailable (no printer to print on)
75 Path/file access error
76 Path not found
91 Object variable not set (property, method, Show/Hide/Unload on a value that is not a form or control, or on Nothing)
+ 93 Invalid pattern string (a LIKE pattern with a [ that has no ])
94 Invalid use of Null (Null where a real number or text is needed; see Variants)
424 Object required (SET, Is or TypeOf with a value that is not an object)
438 Object doesn't support this property or method (the object has no member of that name, or it is Private)
+ 446 Object doesn't support named arguments (a form or control method called with Name:=value)
+ 448 Named argument not found (a Name:= the method does not have; a left-out OPTIONAL Variant argument, Missing, used as a value)
+ 449 Argument not optional (a method call through an object left out a parameter that is not OPTIONAL)
450 Wrong number of arguments or invalid property assignment
451 Object not a collection (FOR EACH over an object that is not a Collection)
457 This key is already associated with an element of this collection (Collection.Add with a key in use)
+ 481 Invalid picture (LOADPICTURE of a file that is not a picture DVX can show)
1000 No form runtime (a form or control statement ran without the form runtime, e.g. under a headless runner)
Executes an operating-system command.
@@ -2551,8 +2839,14 @@ PLAY "T160 O3 L8 E D C D E E E4" MS Music staccato: notes sound for 3/4 of their length. MF Music foreground: PLAY, SOUND and BEEP wait until the sound has finished (the default). MB Music background: the program goes on while the music plays; it only waits when more than 32 notes are waiting. - I n DVX's own: play the notes that follow on General MIDI instrument n (1 to 128; 1 is the piano, 41 the violin, 74 the flute) through the music synthesizer. I0 goes back to plain tones (the default). -A string with a mistake in it plays nothing and raises error 5 (Illegal function call). QBasic's X command (play a string variable) and the "=" form of numbers are not supported; build the string with + instead, such as PLAY "O" + STR$(n). Without I, the notes are square-wave tones like the PC speaker's; with it, they play on the same synthesizer as MIDI music.
+ I n DVX's own: play the notes that follow on General MIDI instrument n (1 to 128; 1 is the piano, 41 the violin, 74 the flute) through the music synthesizer. I0 goes back to plain tones (the default). + X "X" + VARPTR$(s$) plays the commands in s$ at that point (s$ may use X itself, up to 16 levels deep). + = Any number above may be written as "=" + VARPTR$(n), n a numeric expression: "T=" + VARPTR$(tempo). +A string with a mistake in it plays nothing and raises error 5 (Illegal function call). X and "=" work as in QBasic, with VARPTR$ as for DRAW (see the program screen's DRAW): the value is taken when VARPTR$ runs. A whole tune may be at most 32767 characters once X and "=" are filled in.
+verse$ = "L8 E D C D E E E4"
+tempo = 160
+PLAY "T=" + VARPTR$(tempo) + "O3 X" + VARPTR$(verse$) + "X" + VARPTR$(verse$)
+Building the string with + works too, such as PLAY "O" + STR$(n). Without I, the notes are square-wave tones like the PC speaker's; with it, they play on the same synthesizer as MIDI music.
PLAY "MB T120 L4 CDEF GAB>C" ' a scale in the background
PLAY "I74 O4 L8 GAB>C" ' on the flute
These statements work as they do in QBasic. The first one a program uses gives it a screen of its own: a window, titled with the program's name, the size of the screen mode. From then on PRINT and INPUT use that screen too (before it, PRINT writes to the output window and INPUT asks in a dialog box, as always). Keys typed in the window go to INKEY$ and INPUT. The window stays open when the program's code has finished, so its output can be read; closing it ends the program.
The names are not reserved: a program that has its own variable, SUB or constant called Color, Cls, Draw and so on keeps using it.
Mode Size and colours Text - ---- ---------------- ---- - SCREEN 0 Text only, 16 colours 80 columns, 25 rows (WIDTH 80, 50 for 50 rows) - SCREEN 12 640 x 480 graphics, 16 colours 80 columns, 30 rows (WIDTH 80, 60 for 60 rows) - SCREEN 13 320 x 200 graphics, 256 colours 40 columns, 25 rows-
A program starts in SCREEN 0. SCREEN mode (and WIDTH) clears the screen and restores the mode's colours: the 16 standard colours, and in SCREEN 13 the standard 256-colour palette. Other mode numbers, and graphics statements in SCREEN 0, are error 5 (Illegal function call). SCREEN's colour switch and page arguments are accepted and ignored (there is one page), and PCOPY is not supported.
-SCREEN mode
+ Mode Size and colours Text (WIDTH choices) Pages
+ ---- ---------------- -------------------- -----
+ SCREEN 0 Text only, 16 colours 80 or 40 x 25, 43 or 50 8 (4 at 80 x 43, 80 x 50)
+ SCREEN 1 320 x 200, 4 colours (two palettes) 40 x 25 1
+ SCREEN 2 640 x 200, 2 colours 80 x 25 1
+ SCREEN 7 320 x 200, 16 colours 40 x 25 8
+ SCREEN 8 640 x 200, 16 colours 80 x 25 4
+ SCREEN 9 640 x 350, 16 of 64 colours 80 x 25 or 80 x 43 2
+ SCREEN 10 640 x 350, 4 shades (some blinking) 80 x 25 or 80 x 43 2
+ SCREEN 11 640 x 480, 2 colours 80 x 30 or 80 x 60 1
+ SCREEN 12 640 x 480, 16 colours 80 x 30 or 80 x 60 1
+ SCREEN 13 320 x 200, 256 colours 40 x 25 1
+A program starts in SCREEN 0. SCREEN with a new mode (and WIDTH) clears the screen and restores the mode's colours: the 16 standard colours, and in SCREEN 13 the standard 256-colour palette. SCREEN with the mode the program is already in only changes the pages, so a program can flip pages in a loop. Other mode numbers, and graphics statements in SCREEN 0, are error 5 (Illegal function call). The colour switch argument is accepted and ignored.
+Text uses 16-pixel-high characters in SCREEN 0 (25 rows), 11 and 12 (30 rows), 14-pixel ones in SCREEN 9 and 10 (25 rows) and 8-pixel ones everywhere else. The window shows each mode in the shape it has on a monitor: the 640 x 200 modes with every line doubled, 40-column text with every column doubled; SCREEN 9 and 10 show at 640 x 350 as they are. On the whole screen the 320 x 200 modes use the computer's 320 x 200 mode and the others the nearest mode, centred.
+SCREEN mode [, [colorswitch] [, [activepage] [, visualpage]]]
WIDTH [columns] [, rows]
+Screen Pages and PCOPY
+The pages of a mode (see the table) are separate screens. Drawing and PRINT go to the active page; the window shows the visual page. Drawing on a page that is not shown and then showing it (or copying it to the shown page) gives smooth animation. A visual page left out follows the active page. In SCREEN 0 each page has its own text. Each page also keeps its own text cursor, as in QBasic: LOCATE and PRINT move the active page's cursor, and switching pages brings back the cursor where it was on that page.
+SCREEN 7, , 1, 0 ' draw on page 1, show page 0
+Do
+ CLS
+ CIRCLE (x, 100), 20, 14
+ PCOPY 1, 0 ' show the finished picture
+ x = (x + 2) MOD 320
+ FRAMEWAIT 30
+Loop
+PCOPY source, destination copies one page (text and pixels) onto another. A page number the mode does not have is error 5.
FULLSCREEN
Shows the program's screen on the whole screen, or back in its window. Alt+Enter in the window does the same. While the program has the whole screen, Alt+Tab brings the desktop back (the program keeps running in its window) and switching back to the program returns it to the whole screen. Other programs keep running either way. When the computer has no suitable video mode, the screen stays in its window.
FULLSCREEN [ON | OFF]
@@ -2610,30 +2923,51 @@ row = CSRLIN
column = POS(0)
code = SCREEN(row, column [, colorflag])
-- CLS clears the screen and puts the cursor in the top-left corner. In a graphics mode with a VIEW set, CLS (or CLS 1) clears only the view; CLS 0 and CLS 2 clear everything.
+- CLS clears the screen and puts the cursor in the top-left corner (the top of the text viewport). In a graphics mode with a VIEW set, CLS (or CLS 1) clears only the view; with a VIEW PRINT set, CLS (or CLS 2) clears only the text viewport; CLS 0 clears everything.
- LOCATE moves the cursor (rows and columns count from 1). cursor 1 shows the text cursor in SCREEN 0, 0 hides it (the default). QBasic's cursor size arguments are accepted and ignored.
-- COLOR in SCREEN 0 takes a foreground 0 to 31 (16 to 31 blink in QBasic; here they show steady) and a background 0 to 15. In SCREEN 12 and 13 it takes only a foreground (0 to 15, or 0 to 255 in SCREEN 13), which is also the default colour of the drawing statements.
+- COLOR in SCREEN 0 takes a foreground 0 to 31 and a background 0 to 15. Foregrounds 16 to 31 blink (colour minus 16); as on a VGA, backgrounds 8 to 15 show as 0 to 7 and make the text blink too.
+- COLOR in SCREEN 1 takes a background colour (0 to 15) and a palette: an even number gives green, red and brown for colours 1 to 3, an odd one (the default) cyan, magenta and white. SCREEN 2 has no COLOR statement. In SCREEN 7 to 10 COLOR takes a foreground and a background colour (the colour of attribute 0: 0 to 15 in SCREEN 7 and 8, an EGA colour 0 to 63 in SCREEN 9, a shade 0 to 8 in SCREEN 10). In SCREEN 11 to 13 it takes only a foreground. The foreground is also the default colour of the drawing statements.
- CSRLIN is the cursor's row and POS(0) its column. Before the program has a screen, POS(0) is the output window's column.
- SCREEN(row, column) is the character code shown there; with a colorflag of 1, its colour (in SCREEN 0, foreground + 16 * background).
-- PRINT scrolls the screen up at the bottom. CHR$(13) and CHR$(10) start a new line and CHR$(9) moves to the next multiple of 8 columns.
+- PRINT scrolls the screen (or the text viewport) up at the bottom. CHR$(13) and CHR$(10) start a new line and CHR$(9) moves to the next multiple of 8 columns.
+VIEW PRINT
+Makes rows top to bottom the text viewport: PRINT scrolls only those rows, so the rows around them stay as they are (a title, a status line). The cursor goes to the viewport's first row. VIEW PRINT alone makes the whole screen the viewport again. LOCATE can still reach every row.
+VIEW PRINT [top TO bottom]
INKEY$
-Returns the next key typed in the program's window without waiting: "" when there is none, one character for a character key, and CHR$(0) + CHR$(scan code) for other keys (arrows, function keys). Keys wait for INKEY$ in a queue of 64.
+Returns the next key typed in the program's window without waiting: "" when there is none, one character for a character key, and CHR$(0) + CHR$(scan code) for other keys (arrows, function keys). Keys held with Alt, and function keys held with Shift, Ctrl or Alt, give the codes QBasic gives: Alt+A is CHR$(0) + CHR$(30), Alt+1 CHR$(0) + CHR$(120), Shift+F1 CHR$(0) + CHR$(84), Ctrl+F1 CHR$(0) + CHR$(94) and Alt+F1 CHR$(0) + CHR$(104). Keys wait for INKEY$ in a queue of 64.
Do
k$ = INKEY$
Loop While k$ = ""
If k$ = Chr$(0) + Chr$(72) Then Print "Up arrow"
+PEN
+QBasic's light pen; the mouse on the program's screen stands in for it, as the mouse drivers of the time did. PEN ON lets the program read it, PEN OFF stops that (PEN STOP is accepted too). PEN(n) then tells:
+ n PEN(n)
+ - ------
+ 0 True (-1) if the left button went down since PEN(0) was last read, else 0
+ 1, 2 x and y (pixels) where it last went down
+ 3 True (-1) while the left button is down, else 0
+ 4, 5 x and y where the mouse last was over the screen
+ 6, 7 text row and column where it last went down
+ 8, 9 text row and column where it last was
+PEN(n) before PEN ON, after PEN OFF, or with another n raises error 5.
+Screen 12
+Pen On
+Do
+ If Pen(0) Then PSet (Pen(1), Pen(2)), 14
+Loop While InKey$ = ""
INPUT and LINE INPUT on the Screen
On the program's screen INPUT shows its prompt at the cursor and reads what is typed there: Backspace takes back a character, Esc clears the line and Enter ends it. LINE INPUT reads a whole line, commas included, without adding "? " to the prompt. Without the program's screen LINE INPUT asks in a dialog box, like INPUT.
LINE INPUT ["prompt";] variable$
Graphics Coordinates
-Points are (x, y) in pixels from the top-left corner. STEP(x, y) is relative to the last point drawn (the centre of the screen at first). VIEW makes a smaller part of the screen the drawing area (coordinates start at its corner, and drawing stays inside it); WINDOW gives it coordinates of your own. Colour numbers are attributes: 0 to 15 in SCREEN 12, 0 to 255 in SCREEN 13; anything else is error 5.
+Points are (x, y) in pixels from the top-left corner. STEP(x, y) is relative to the last point drawn (the centre of the screen at first). VIEW makes a smaller part of the screen the drawing area (coordinates start at its corner, and drawing stays inside it); WINDOW gives it coordinates of your own. Colour numbers are attributes: 0 to 3 in SCREEN 1 and 10, 0 and 1 in SCREEN 2 and 11, 0 to 15 in SCREEN 7, 8, 9 and 12, 0 to 255 in SCREEN 13; anything else is error 5.
VIEW [[SCREEN] (x1, y1)-(x2, y2) [, [fill] [, border]]]
-WINDOW [[SCREEN] (x1, y1)-(x2, y2)]
+WINDOW [[SCREEN] (x1, y1)-(x2, y2)]
+value = PMAP(coordinate, n)
PSET [STEP](x, y) [, color]
@@ -2654,8 +2988,13 @@ LINE (0, 100)-(319, 100), 7, , &HF0F0
CIRCLE (160, 100), 50, 4, -0.0001, -1.5708 ' a quarter pie slice
CIRCLE (160, 100), 80, 2, , , 0.5 ' an ellipse
PAINT [STEP](x, y) [, [color] [, border]]
-Fills the area around the point with color, up to pixels of the border colour (by default the fill colour itself). Tile patterns (a string instead of a colour) are not supported.
+PAINT [STEP](x, y) [, [color] [, border]]
+PAINT [STEP](x, y), tile$ [, [border] [, background$]]
+Fills the area around the point with color, up to pixels of the border colour (by default the fill colour itself).
+With a string instead of a colour, PAINT fills the area with a pattern, as in QBasic. Each row of the pattern is one byte for each colour plane of the mode, and each bit is a pixel (the first bit is the left pixel): in SCREEN 2 and 11 one byte is eight pixels, colour 0 or 1; in SCREEN 7, 8, 9 and 12 four bytes (the blue, green, red and bright planes) make eight pixels of 16 colours; in SCREEN 10 two bytes. In SCREEN 1 each byte is four pixels of two bits, and in SCREEN 13 each byte is one pixel. A pattern has up to 64 rows and repeats over the screen. The fill stops at the border colour (by default the COLOR foreground) and covers the whole area once, even where it already has the pattern's colours. background$ is accepted as in QBasic: a pattern with more than two rows in a row equal to it is error 5.
+SCREEN 12
+LINE (0, 0)-(100, 100), 15, B
+PAINT (50, 50), CHR$(&HAA) + CHR$(&H55) + CHR$(0) + CHR$(0), 15 ' blue and green stripes
Draws with a small command language. Letters may be upper or lower case; spaces and semicolons are ignored.
Command Meaning @@ -2676,13 +3015,18 @@ CIRCLE (160, 100), 80, 2, , , 0.5 ' an ellipseA n Turn the directions by n * 90 degrees (0 to 3). TA n Turn the directions by n degrees (-360 to 360). P c,b PAINT at the current point with colour c up to border colour b. -
The pen starts at the last point and stays where it ends. The colour, scale and turn stay from one DRAW to the next. QBasic's X command (draw a string variable) and the "=" form of numbers are not supported; build the string with + instead, such as DRAW "R" + STR$(n). A mistake in the string is error 5.
+The pen starts at the last point and stays where it ends. The colour, scale and turn stay from one DRAW to the next. A mistake in the string is error 5.
+As in QBasic, X runs another DRAW string and "=" takes a number, both given with VARPTR$: DRAW "X" + VARPTR$(shape$) and DRAW "C=" + VARPTR$(c) + "R=" + VARPTR$(n). DVX BASIC has no memory addresses, so VARPTR$ packs the value itself into its result (taken when VARPTR$ runs), and it takes any expression. Building the string with + and STR$ works too, such as DRAW "R" + STR$(n).
DRAW "BM100,100 C14 R50 D50 L50 U50" ' a square
-DRAW "BM160,100 TA45 C2 R40" ' a line at 45 degrees
+DRAW "BM160,100 TA45 C2 R40" ' a line at 45 degrees
+box$ = "R10 D10 L10 U10"
+FOR i = 1 TO 5
+ DRAW "BM" + STR$(i * 20) + ",20 X" + VARPTR$(box$)
+NEXT i
PALETTE [attribute, color]
PALETTE USING array[(first)]
-Changes the colour an attribute shows. In SCREEN 12 and 13, color is blue * 65536 + green * 256 + red with each part 0 to 63; in SCREEN 0 it is an EGA colour number 0 to 63. PALETTE alone restores the mode's colours. PALETTE USING sets every attribute (16, or 256 in SCREEN 13) from a one-dimensional LONG array starting at element first (or its first element); -1 leaves that attribute as it is. On the whole screen a palette change shows at once, like on a VGA card; in the window, whatever is already drawn in that attribute changes colour too.
+Changes the colour an attribute shows. In SCREEN 11, 12 and 13, color is blue * 65536 + green * 256 + red with each part 0 to 63; in SCREEN 0 and 9 it is an EGA colour number 0 to 63; in SCREEN 1, 2, 7 and 8 one of the 16 standard colours; in SCREEN 10 a shade 0 to 8 (0 off, 4 grey, 8 bright, and 1 to 3, 5 to 7 blinking between two of them). PALETTE alone restores the mode's colours. PALETTE USING sets every attribute of the mode (256 in SCREEN 13) from a one-dimensional LONG array starting at element first (or its first element); -1 leaves that attribute as it is. On the whole screen a palette change shows at once, like on a VGA card; in the window, whatever is already drawn in that attribute changes colour too.
SCREEN 13
PALETTE 1, 63 * 65536 ' attribute 1 becomes bright blue
FOR i = 0 TO 63
@@ -2727,11 +3071,23 @@ Print #1, "Line one"
Print #1, "Line two"
Close #1 ' now it prints
Closes an open file channel.
-CLOSE #channel
+Closes open file channels.
+CLOSE #channel
+CLOSE #channel, #channel, ...
+CLOSE
+RESET
+CLOSE with a list closes each channel in it (the # is optional); CLOSE alone, and RESET, close every open file. Closing a channel that is not open does nothing; a number outside the channels DVX has raises error 52.
Writes text to a file, formatted exactly as PRINT shows it (numbers are followed by a space). See PRINT.
-PRINT #channel, expression
+Writes text to a file, formatted exactly as PRINT shows it: numbers are followed by a space, a comma moves to the next 14-column zone, a semicolon keeps the line going, and TAB(n), SPC(n) and PRINT #channel, USING work as they do for PRINT. See PRINT.
+PRINT #channel, expression [; | , expression ...]
+PRINT #channel, USING format$; expression [; expression ...]
+Sets the line width of an open file, the screen or the printer: output longer than the width goes on in a new line, as in QBasic. 255 means no limit (the default).
+WIDTH #channel, width
+WIDTH "SCRN:", width
+WIDTH "LPT1:", width
+WIDTH LPRINT width
+WIDTH "SCRN:" is the same as WIDTH width for the program's screen. WIDTH "LPT1:" (LPT2:, LPT3:, PRN) and WIDTH LPRINT set the width of the printer's lines, for LPRINT and for files opened on that printer afterwards. A width outside 1 to 255 raises error 5, a file number that is not open error 52, and a device DVX does not have (such as "COM1:") error 64.
Reads comma-delimited data from a file, one field per variable. Quoted strings written by WRITE # are read back without their quotes; numeric variables receive the converted value.
INPUT #channel, variable [, variable ...]
@@ -2750,7 +3106,7 @@ Close #1
Read and write records in RANDOM or BINARY mode files. When recordNum is omitted the transfer starts at the current file position.
GET #channel, [recordNum], variable
PUT #channel, [recordNum], variable
-In RANDOM mode recordNum is a 1-based record number and each record is LEN bytes (see OPEN). Numbers are stored in their binary form (Integer and Boolean 2 bytes, Long 4, Single 4, Double 8); a String is stored as a 2-byte length followed by its characters. PUT always writes a whole record, padding the unused part with zero bytes, and GET always advances to the next record; data longer than LEN raises error 59 before anything is written.
+In RANDOM mode recordNum is a 1-based record number and each record is LEN bytes (see OPEN). Numbers are stored in their binary form (Byte 1 byte, Integer and Boolean 2, Long 4, Single 4, Double, Currency and Date 8); a String is stored as a 2-byte length followed by its characters. PUT always writes a whole record, padding the unused part with zero bytes, and GET always advances to the next record; data longer than LEN raises error 59 before anything is written.
In BINARY mode recordNum is a 1-based byte position. Numbers are stored as in RANDOM mode. PUT writes the characters of a String with no length prefix, and GET reads as many bytes as the String variable currently holds, so set its length first (for example with SPACE$ or by declaring it as STRING * n).
The variable may be a TYPE variable. Its fields are packed in declaration order with the sizes above; a STRING * n field takes exactly n bytes (padded with spaces), a variable-length String field is stored with its 2-byte length prefix in both modes, and nested TYPE fields are packed inline. GET fills the fields of the existing variable in place.
The variable may also be an array element, arr(i), of any type including a TYPE: PUT writes that element and GET reads into it. The index expression is evaluated once.
@@ -2836,10 +3192,10 @@ WENDFunction Returns Description -------- ------- ----------- - ASC(s$) Integer ASCII code of the first character of s$ + ASC(s$) Integer ASCII code of the first character of s$ (error 5 for an empty string) CHR$(n) String Character with ASCII code n FILTER(arr, match$ [, include [, compare]]) String array The elements of arr that contain match$ (or, with include False, that do not) - FORMAT$(value, fmt$) String Formats a number or a date using a format string + FORMAT$(value [, fmt$]) String Formats a number or a date using a format string HEX$(n) String Hexadecimal representation of n (uppercase, no leading &H) INSTR(s$, find$) Long Position of find$ in s$ (1-based), 0 if not found INSTR(start, s$, find$) Long Search starting at position start (1-based) @@ -2888,7 +3244,8 @@ Print Join$(parts, " / ") ' red / green / / blueINSTRREV searches backward: INSTRREV("abcabc", "bc") is 5 and INSTRREV("abcabc", "bc", 4) is 2. A start of -1 (the default) searches from the end. It returns 0 when s$ is empty or start is past the end of s$, and start when find$ is empty. A start of 0 or below -1 raises error 5.
FORMAT$
FORMAT$ formats a numeric value using a BASIC-style format string. The format characters are the same as the ones used by PRINT USING.
-+s$ = FORMAT$(value, fmt$)s$ = FORMAT$(value, fmt$) +s$ = FORMAT$(value)Print Format$(3.14159, "###.##") ' " 3.14" Print Format$(-7, "+###") ' " -7" Print Format$(42, "00000") ' "00042" @@ -2896,6 +3253,7 @@ Print Format$(1234.5, "#,##0.00") ' "1,234.50" Print Format$(5, "#,##0.00") ' " 5.00" Print Format$(0.5, "PERCENT") ' "50%"The whole format string is one numeric field (for a number). The accepted format characters are # (digit or pad space), 0 (digit that is always printed; zero pads up to the first 0), . (decimal point), , (thousands separator), + and - (sign placement), $$ (floating dollar sign), ** (asterisk fill), ^^^^ (scientific notation) and the literal word PERCENT (multiplies by 100 and appends %). Unlike PRINT USING, a value wider than the field is printed in full without a % marker. See PRINT USING for details on each character.
+With no format string (or an empty one), FORMAT$ gives the value's plain text, as CSTR does: a number without the leading space STR$ puts before it (Format$(42) is "42"), a date in the usual m/d/yyyy h:mm:ss AM form, text as it is, True / False, and "" for Empty.
FORMAT (without the $) is the same function.
FORMAT$ with Dates
When the format string holds a date or time code, or is one of the named date formats, FORMAT$ formats its value as a date. The value may be a Date, a number (taken as a date's number of days) or a string that holds a date (anything else raises error 13). A Date with an empty format string gives the usual m/d/yyyy h:mm:ss AM form. The codes may be written in upper or lower case.
@@ -2967,23 +3325,27 @@ Print s$ ' Hello, DVX--!
INT, FIX and SGN return an Integer, widening to Long or Double when the value does not fit. EXP, ^ and the other functions raise error 6 (Overflow) when the result leaves the numeric range, and error 5 when it is undefined (for example a negative number to a fractional power).
Note: Use RANDOMIZE or RANDOMIZE TIMER to seed the generator so you don't get the same sequence each run.
The color functions work with 24-bit RGB colors packed into a Long.
+The color functions work with 24-bit colors packed into a Long the way Visual Basic packs them, &HBBGGRR: red is the low byte, green the middle byte and blue the high byte. RGB(255, 0, 0) and &HFF are red, RGB(0, 0, 255) and &HFF0000 are blue. ForeColor, BackColor, FillColor, the drawing methods of forms and picture boxes (PSet, Line, Circle, Point), the Canvas control and the color dialog all use this form (the console graphics statements of SCREEN modes use QBasic color numbers instead), as do the color constants vbBlack, vbRed, vbGreen, vbYellow, vbBlue, vbMagenta, vbCyan and vbWhite (see Predefined Constants).
Function Returns Description -------- ------- ----------- - RGB(r, g, b) Long Combine red, green, and blue components (0-255) into a 24-bit color - GETRED(color) Integer Red component (0-255) of a color built with RGB - GETGREEN(color) Integer Green component (0-255) of a color built with RGB - GETBLUE(color) Integer Blue component (0-255) of a color built with RGB+ RGB(r, g, b) Long Combine red, green, and blue components (0-255) into a color: r + g * 256 + b * 65536 + QBCOLOR(n) Long The color of QBasic color number n (0 to 15: 0 black, 1 blue, 2 green, 4 red, 7 white, 8 gray, 15 bright white, ...); error 5 for any other number + GETRED(color) Integer Red component (0-255) of a color + GETGREEN(color) Integer Green component (0-255) of a color + GETBLUE(color) Integer Blue component (0-255) of a color
Dim c As Long
-c = RGB(255, 128, 0) ' bright orange
+c = RGB(255, 128, 0) ' bright orange, &H0080FF
Print GetRed(c); GetGreen(c); GetBlue(c) ' 255 128 0
-Me.BackColor = RGB(0, 0, 128) ' dark blue background
+Me.BackColor = RGB(0, 0, 128) ' dark blue background
+Me.ForeColor = QBColor(14) ' yellow
Function Returns Description -------- ------- ----------- CBOOL(n) Boolean Returns True (-1) if n is nonzero or a non-empty string; False (0) otherwise + CBYTE(n) Byte Converts n to Byte with the rounding CINT uses; error 6 (Overflow) outside 0 to 255 + CCUR(n) Currency Converts n to Currency, rounded to four decimal places (halves to even); error 6 (Overflow) outside the Currency range CDATE(v) Date Converts a number (days since 12/30/1899) or date text to a Date; error 13 for text that is not a date, error 6 outside 1/1/100 to 12/31/9999 CDBL(n) Double Converts n to Double (a Date gives its number of days) CINT(n) Integer Converts n to Integer; halves round to the nearest even number (2.5 -> 2, 3.5 -> 4); error 6 (Overflow) outside -32768 to 32767 @@ -2991,7 +3353,7 @@ Me.BackColor = RGB(0, 0, 128) ' dark blue backgroundCSNG(n) Single Converts n to Single CSTR(n) String Converts n to its String representation (a Date gives m/d/yyyy h:mm:ss AM) CVAR(v) Variant Returns v unchanged as a Variant -
Passing Null to any of these except CVAR raises error 94 (Invalid use of Null). VARTYPE, TYPENAME and the IS functions (ISNULL, ISEMPTY, ISNUMERIC, ...) are described under Variants.
+Passing Null to any of these except CVAR raises error 94 (Invalid use of Null). Text passed to one that gives a number must hold a number (CINT("12") is 12); other text raises error 13 (Type mismatch). VARTYPE, TYPENAME and the IS functions (ISNULL, ISEMPTY, ISNUMERIC, ...) are described under Variants.
DATE$ and TIME$ return text; the Date and Time Functions return Date values.
IIF and CHOOSE evaluate every argument before picking one, as in Visual Basic, so a function called in an argument runs even when its value is not chosen. A fractional n in CHOOSE drops its fraction (2.9 picks the second value). An n below 1 or past the end of the list gives Null, as in Visual Basic (test it with ISNULL; see Variants).
Print IIf(score >= 50, "Pass", "Fail")
Print Choose(Weekday(Date), "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat")
+LOADPICTURE(file$) reads a picture file (BMP, PNG, JPEG or GIF) and returns a Picture object (TypeName "Picture") that can be given to a picture property: a control's DragIcon, the Picture of an Image or ImageButton. Use SET, or a plain assignment, as with a file name; file names still work too. LOADPICTURE() or LOADPICTURE("") gives an empty picture, which clears the property. A missing file raises error 53 (File not found), a file that is not a picture error 481 (Invalid picture), so the check happens where the picture is loaded. Loading the same file again gives the same Picture object. Reading a picture property back gives a Picture object too (TypeName "Picture"), as in Visual Basic; used as a value (printed, joined to text, compared) it stands for its file name, so code that treats the property as a file name keeps working.
+The optional size and colordepth arguments are Visual Basic's: size is vbLPSmall (0), vbLPLarge (1), vbLPSmallShell (2), vbLPLargeShell (3) or vbLPCustom (4, with x and y giving the size), colordepth vbLPDefault (0), vbLPMonochrome (1), vbLPVGAColor (2) or vbLPColor (3). They choose among the images of an icon file; every DVX picture is a single bitmap, so, as for a bitmap in Visual Basic, they are checked and otherwise make no difference. A value outside these, or a negative x or y, raises error 5.
+A Picture object has three properties, which can only be read:
+Property Type Description + -------- ---- ----------- + Width Long The picture's width in HiMetric units (hundredths of a millimetre), as in Visual Basic; 0 for an empty picture. + Height Long The picture's height in HiMetric units; 0 for an empty picture. + Type Integer vbPicTypeBitmap (1) for a loaded picture, vbPicTypeNone (0) for an empty one.+
The HiMetric sizes assume a standard display of 96 pixels an inch (the same as ScaleMode), so a pixel is about 26.46 HiMetric units. Convert them to pixels or another scale with a form's or PictureBox's ScaleX and ScaleY methods (see Drawing on Forms and PictureBoxes). Visual Basic's Handle property and Render method are not available: DVX pictures have no system handle.
+Set Label1.DragIcon = LoadPicture("drag.bmp")
+Image1.Picture = LoadPicture("logo.bmp")
+Set Label1.DragIcon = LoadPicture() ' back to the dotted outline
+
+Dim pic As Object
+Set pic = LoadPicture("logo.bmp")
+Print Me.ScaleX(pic.Width, vbHimetric, vbPixels); "x"; Me.ScaleY(pic.Height, vbHimetric, vbPixels)
COMMAND$ returns the program's arguments exactly as they were given, as in Visual Basic (QBasic turned them into capitals; DVX keeps them as they are). Arguments are separated by blanks; an argument with blanks in it is written in double quotes. COMMAND$(n) picks out one argument with its quotes removed, so a program that opens the document it was started with only needs:
IF COMMAND$(1) <> "" THEN
@@ -3315,8 +3697,9 @@ Option Explicit
Method Sub or Function (Public unless Private) obj.Method args, x = obj.Func(args)
Read property Property Get name(...) As type x = obj.name
Write property Property Let name(..., value) obj.name = x
- Object property Property Set name(..., value) Set obj.name = someObject
-A Sub, Function or Property is Public unless it is marked Private; Private procedures can be called only by the class's own code. Inside the class's procedures the member variables are used by name, with no SHARED needed, and each call works with the variables of the object it was called for. The class's code can also use the program's DIM SHARED (or PUBLIC) module variables, procedures and constants.
+ Object property Property Set name(..., value) Set obj.name = someObject + Friend procedure Friend Sub / Function / Property ... obj.name, by the program's own code +A Sub, Function or Property is Public unless it is marked Private; Private procedures can be called only by the class's own code. A Friend one, as in Visual Basic, can be called by the rest of the program through a variable declared as the class, but it is not part of the object's late-bound interface: through As Object or As Variant it raises error 438, and an Implements interface does not include it. Inside the class's procedures the member variables are used by name, with no SHARED needed, and each call works with the variables of the object it was called for. The class's code can also use the program's DIM SHARED (or PUBLIC) module variables, procedures and constants.
A property looks like a variable from outside, but reading or assigning it runs a procedure, so the class can check or calculate the value:
Private mC As Double
@@ -3339,7 +3722,8 @@ End Property
Dim t As New Thermometer
t.Celsius = 100
Print t.Fahrenheit ' 212
-Property Get returns the value: assign it to the property name, as in a FUNCTION. Property Let receives the value being assigned as its last parameter; Property Set does the same for an object assigned with SET. A property can take arguments of its own before the value, such as Property Get Cell(r, c) and Property Let Cell(r, c, v), used as obj.Cell(1, 2) = 7. A property with only a Property Get is read-only: assigning it raises error 438. EXIT PROPERTY leaves a property procedure early.
+Property Get returns the value: assign it to the property name, as in a FUNCTION. Property Let receives the value being assigned as its last parameter; Property Set does the same for an object assigned with SET. A property can take arguments of its own before the value, such as Property Get Cell(r, c) and Property Let Cell(r, c, v), used as obj.Cell(1, 2) = 7. A property with only a Property Get is read-only: assigning it is a compile error through a variable of the class (error 438 through As Object). EXIT PROPERTY leaves a property procedure early.
+Property procedures can also be written in an ordinary module, as in Visual Basic. The property is then used by its name like a variable, anywhere in the program: Size = 4 runs Property Let Size, Print Size runs Property Get Size, and Item(2) = "two" passes 2 and the value to Property Let Item.
An object variable starts as Nothing. NEW makes an object:
Dim a As Account ' Nothing so far
@@ -3353,22 +3737,38 @@ Dim b As Account
Set b = a ' two references to one account
Set a = Nothing ' still alive: b refers to it
Set b = Nothing ' last reference gone: Class_Terminate runs now
-Warning: Objects that refer to each other, directly or around a longer loop, keep each other alive even after the program's own variables let them go. Such objects stay in memory until the program ends, and their Class_Terminate never runs. Break the loop yourself (for example Set a.Partner = Nothing) before letting go of the objects.-
When the program ends (END, or the last form closes), every object still alive is freed without running Class_Terminate, as with END in Visual Basic. Work that must always happen, such as saving a file, belongs in a method the program calls itself, not only in Class_Terminate.
+Objects that refer to each other, directly or around a longer loop, keep each other alive even after the program's own variables let them go. DVX BASIC finds such groups while the program runs: after every thousand or so new objects (more when the program keeps many objects), and when the program's code runs to its end, it looks for objects that only the group itself still refers to. Each of those whose class has a Class_Terminate gets it called once, oldest object first, with every object of the group still intact; afterwards the group is freed. An object that a Class_Terminate stores somewhere (Set saved = Me) stays alive, and its Class_Terminate does not run again. Breaking the loop yourself (for example Set a.Partner = Nothing) still frees the objects at once instead of at the next check.
+When the program ends normally -- its code runs to the end, and with forms the last form is unloaded -- the module variables are released as in Visual Basic, so the objects only they still hold get their Class_Terminate then. When the program ends with END (or is stopped), every object still alive is freed without running Class_Terminate, as with END in Visual Basic. Work that must always happen, such as saving a file, belongs in a method the program calls itself, not only in Class_Terminate.
Inside a class's procedures, Me is the object the procedure was called for. Use it to pass the object to other code, or to go through the object's own properties (Me.Balance runs Property Get Balance):
Public Sub Move(ByVal dx As Double)
Me.X = Me.X + dx ' uses Property Get X and Property Let X
End Sub
Members are found by name when the program runs (late binding): obj.Deposit 5 looks for a Public member named Deposit in whatever object obj holds at that moment. A misspelled or Private member is therefore not reported when the program is compiled but raises error 438 when the line runs, and the wrong number of arguments raises error 450. Using a member of Nothing raises error 91.
+When the compiler knows an object's class -- a variable, array, parameter, member variable or FUNCTION declared As the class name or As Collection, New ClassName, or Me in a class module -- its members are found when the program is compiled (early binding), as in Visual Basic. A misspelled member, a Private one, a Sub used where a value is needed, a property used as a statement, assigning a property that has no Property Let or Set, reading one that has no Property Get, and the wrong number of arguments are then compile errors that point at the member. Calls made this way are also faster. A class's own members can be used anywhere in the class, also above the line that declares them, and through Me a class reaches its Private members as well (Me.mTotal).
+Through a variable declared As Object or As Variant the member is found by name when the program runs (late binding): obj.Deposit 5 looks for a Public member named Deposit in whatever object obj holds at that moment. A misspelled or Private member then raises error 438 when the line runs, and the wrong number of arguments raises error 450. Using a member of Nothing raises error 91 either way. An object of another class that reaches a variable of a class anyway (for example through a ByRef As Object argument) has its members found by name, as late binding would.
+An argument that can never fit an object parameter of a SUB, FUNCTION or method is a compile error too: a number or text, or an object of another class than the parameter's (unless that class Implements it).
An object can be kept in a variable declared as its class name, As Object, or As Variant, in an array, in a TYPE field, in a Collection, or in another object's member variable. It can be passed to a SUB or FUNCTION and returned from a FUNCTION (assign the result with SET inside the function, Set MakeAccount = acct). TYPENAME gives an object's class name, and TypeOf obj Is ClassName tests it; Is tells whether two variables refer to the same object.
-Arguments of a class's procedures follow the usual rules when the object variable is declared as the class name: a parameter without BYVAL gets the caller's variable, so the procedure can change it. When the object is reached through a variable declared As Object or As Variant, every argument is passed by value. An argument left out in the middle of a call (obj.Report a, , c) arrives as Empty; OPTIONAL parameters left out at the end get their type's usual empty value (0, "" and so on).
+Arguments of a class's procedures follow the rules of SUB and FUNCTION calls when the object variable is declared as the class name: a parameter without BYVAL gets the caller's variable, so the procedure can change it, an omitted OPTIONAL argument gets its default, and a ParamArray collects the rest. When the object is reached through a variable declared As Object or As Variant, a variable or array element given as an argument is passed by reference too if the procedure's parameter is not BYVAL (Visual Basic's rule; other expressions go by value). What the procedure stores in such a parameter is converted to the type of the caller's variable (an Integer stays an Integer, as in Visual Basic). A TYPE field given as an argument (obj.Move rec.x, obj.Move pts(1).x) is passed by reference as well (a BYVAL parameter gets its value). The methods of forms and controls always take their arguments by value. An argument left out in the middle of a call (obj.Report a, , c) or at the end is an OPTIONAL parameter's default value, else its empty value: 0, "" and so on for a typed parameter, Missing for a Variant (ISMISSING is True). Leaving out a parameter that is not OPTIONAL raises error 449 (Argument not optional).
An object parameter is itself passed like any other parameter: without BYVAL, SET on the parameter changes the caller's variable (a SUB can make a new object for the caller); with BYVAL, the procedure gets its own reference to the same object, and SET on it does not affect the caller.
A class can name one property or method as its default member, used when the object is written with parentheses directly after it. Put this line in the class, inside the procedure (as Visual Basic writes it) or on its own at the top of the class:
Attribute Item.VB_UserMemId = 0
-With Item as the default member, obj(3) means obj.Item(3), and obj(3) = x calls Property Let Item. The object name on its own, without parentheses, still means the object itself. Collection's default member is Item, which is why c(1) and c("key") work.
+With Item as the default member, obj(3) means obj.Item(3), and obj(3) = x calls Property Let Item. Collection's default member is Item, which is why c(1) and c("key") work. A call's result can be indexed directly, too: c(1)(0) is element 0 of the array item 1 holds, and Split(s, ",")(2) the third part.
+Where a value is needed -- in arithmetic, a comparison, text, PRINT, a typed variable or a typed parameter -- an object stands for its default member's value, as in Visual Basic: with Value as the default member, total = obj + 1 reads obj.Value, and obj = 9 (without SET) stores 9 through it. A member variable can be the default member too (Attribute Label.VB_UserMemId = 0). An object without a default member used as a value raises error 13. The object name on its own still means the object itself wherever an object is expected: SET, Is, passing it to an object parameter, adding it to a Collection. Controls have default properties as well (Text1 means Text1.Text); see the control overview.
+WITH names an object or a TYPE record once for a block of statements; inside it, a name starting with a dot (.Value, .Add 3) is that object's member or that record's field:
+With acct
+ .Owner = "Ada"
+ .Deposit 100
+ Print .Balance
+End With
+
+With shapes(i).origin ' a TYPE record: its fields
+ .x = 5
+ .y = .x * 2
+End With
+The expression after WITH is worked out once, when the block starts: With New Account makes one object that lives until End With, and With list(i) keeps the element it named even if i changes inside. WITH blocks can be nested; a dot then means the innermost one. A word after a space that starts with a dot is a new argument of its own (Bump .Count passes the member to Bump). Me works too (With Me). Leaving the block by GOTO or EXIT is allowed.
A class reports a problem with ERROR, normally numbered from vbObjectError so that its numbers cannot be mistaken for the built-in ones. The error is handled by the ON ERROR handler of the code that called the class, as with any procedure:
' in the class
@@ -3388,15 +3788,118 @@ NoMoney:
- A member's name cannot be a reserved word. Names such as Name, Show, Hide, Load, Print, Open, Close, Line, Input, Get, Put, Type, Error and Timer are keywords in DVX BASIC and cannot be used; Text, Caption, Value, Count, Item and Add are fine.
- SHARED and DIM SHARED cannot be used in a class module; a class's code reaches the program's module variables that are declared DIM SHARED or PUBLIC.
-- A STATIC variable in a class procedure is shared by all objects of the class, not kept per object. Use a member variable for a value each object keeps.
+- A STATIC variable in a class procedure is kept per object, as in Visual Basic: each object has its own copy.
- Class_Initialize and Class_Terminate take no parameters.
- A class module cannot hold statements outside its procedures; the compiler reports "Only declarations are allowed outside procedures in a class module".
- NEW, PROPERTY, PUBLIC, PRIVATE and TYPEOF are reserved words. VARIANT, OBJECT, COLLECTION, EMPTY, NULL, EACH and IN are not.
+
+
A class module can announce that something happened -- a download finished, a value changed, a step of a long job was done -- and let the code that uses its objects react, the way a form reacts to a button's Click. The class declares the event and raises it; the program declares a variable WithEvents and writes a SUB for each event it wants to handle.
+In the class module, declare each event at the top with EVENT, then raise it from any of the class's procedures with RAISEEVENT:
+' class module Downloader
+Public Event Progress(ByVal percent As Integer, cancel As Boolean)
+Public Event Done()
+
+Public Sub Start()
+ Dim stopNow As Boolean
+ Dim p As Integer
+ For p = 10 To 100 Step 10
+ RaiseEvent Progress(p, stopNow)
+ If stopNow Then Exit Sub
+ Next
+ RaiseEvent Done
+End Sub
+An event may have parameters but no return value. Its parameters cannot be OPTIONAL or a PARAMARRAY, and an event is always public (Public Event and Event mean the same; Private Event is an error). RAISEEVENT can only be used inside the class's own procedures, with exactly the arguments the event declares. A parameter without BYVAL is passed by reference, so a handler can change the variable given for it -- the cancel flag above is how a handler asks the class to stop. This holds for a variable, an array element (a(i)) and a TYPE field (rec.f, arr(i).f), as in Visual Basic. When several handlers get the event they all get the same variable, so each sees what the one before it stored; every store is converted to the variable's or field's type at once, as an assignment would be. While the handlers run, the arrays whose elements (or elements' fields) were given are locked: REDIM of one of them (or ERASE of a dynamic one) raises error 10. An expression, or a variable in its own parentheses, given for a by-reference parameter is passed as one temporary value that every handler shares, as in Visual Basic: the variable itself does not change, but a handler sees what the one before it stored. An argument is converted to the type of a typed event parameter, with the rules of SUB calls (a variable of another type given by reference is a compile error).
+In a module, a form or another class module, declare a variable of the class WithEvents, outside any procedure, and write a SUB named variable_EventName with the same parameters as the event:
+Dim WithEvents dl As Downloader
+
+Sub StartDownload()
+ Set dl = New Downloader
+ dl.Start
+End Sub
+
+Sub dl_Progress(ByVal percent As Integer, cancel As Boolean)
+ Label1.Caption = percent & "%"
+ If percent >= 50 And StopWanted Then cancel = True
+End Sub
+
+Sub dl_Done()
+ MsgBox "Finished"
+End Sub
+The handler's parameters must match the event's: the same number, the same types, and the same BYVAL / BYREF choice, or the program does not compile. A handler is optional: an event nobody handles does nothing. RAISEEVENT calls the handler of every WithEvents variable that holds the raising object at that moment -- two variables holding the same object both get the event, in the order they were declared -- and waits for them to finish before the class's code continues. Setting the variable to another object (or to Nothing) moves the handlers to the new object at once.
+A WithEvents variable in a form belongs to that form: its handlers run with the form's own variables and controls, even when the event is raised while another form's code is running, and a form that is loaded twice gets its own variable each time. Unloading the form stops its handlers.
+A class can handle the events of objects it holds. The variable is a member variable (usually Private) and the handlers are the class's own procedures; each object of the class handles the events of the object in its own variable, and the handler runs for that object (Me):
+' Class module Logger
+Private WithEvents source As Downloader
+Private lines As Integer
+
+Public Sub Watch(d As Downloader)
+ Set source = d
+End Sub
+
+Private Sub source_Progress(ByVal percent As Integer, cancel As Boolean)
+ lines = lines + 1
+ Print "log"; lines; percent
+End Sub
+Lifetime rule: listening never keeps an object alive. The object that raises an event does not hold a reference to its listeners; only the listener's own variable refers to the source. So:
+An interface is a set of properties and methods that several classes promise to provide, so that code can work with any of them through one variable type. In DVX BASIC, as in Visual Basic, the interface is an ordinary class module -- its procedures are usually left empty -- and a class that provides it says IMPLEMENTS:
+' class module IShape
+Public Function Area() As Double
+End Function
+Public Sub Scale(ByVal factor As Double)
+End Sub
+
+' class module Circle
+Implements IShape
+Private r As Double
+
+Private Function IShape_Area() As Double
+ IShape_Area = 3.14159 * r * r
+End Function
+
+Private Sub IShape_Scale(ByVal factor As Double)
+ r = r * factor
+End Sub
+For every public member of the interface, the implementing class writes a procedure named Interface_Member of the same kind (Sub, Function, Property Get / Let / Set) with the same number of parameters, ending in a ParamArray exactly when the interface's does; they are usually Private, so they are only reached through the interface. A call through the interface reaches the implementation directly (the compiler records which procedure implements each member), and an implementation's BYVAL parameter gets a value even where the interface's parameter is ByRef. A public variable of the interface is implemented as a Property Get and a Property Let (Property Set for an object variable). A class that leaves one out does not compile.
+Code then uses the objects through a variable declared as the interface:
+Dim s As IShape
+Set s = New Circle
+s.Scale 2
+Print s.Area ' runs Circle's IShape_Area
+Print TypeOf s Is IShape ' True
+Print TypeName(s) ' Circle
+Through a variable declared as the interface, a member always means the interface's member, even when the class has a public member of the same name of its own. Through the class's own type, As Object or As Variant, the class's own public members are used. SET into a variable of the interface type accepts an object of any class that Implements it (and of the interface class itself); TypeOf obj Is Interface is True for them. A class can implement several interfaces with one IMPLEMENTS line for each. An argument left out of a call through the interface takes the default the interface's procedure declares, as in Visual Basic, even if the implementing procedure declares another. Friend procedures are not part of an interface.
+ + +A Collection is a ready-made object that holds a list of items of any kind -- numbers, text, objects -- in order. Items can be looked up by position or by a text key, added anywhere in the list and removed, and FOR EACH walks through them. The list grows as needed; there is no size to declare.
@@ -3425,14 +3928,15 @@ fruit.Remove 1 ' removes Banana, now the first itemPositions start at 1 and change as items are added and removed. A key must be text, is compared without regard to upper and lower case, and must be unique in the Collection. Give either before or after, not both; leave out the arguments you do not use with commas (c.Add item, , 1 puts item first). Items are stored as they are: an object added to a Collection stays alive while the Collection holds it.
Make a Collection with DIM c AS NEW COLLECTION or SET c = NEW COLLECTION. Like any object, it is freed when the last reference to it goes.
+Through a variable declared As Collection the members are checked when the program is compiled: another member name, the wrong number of arguments, using Add or Remove as a value, Count or Item as a statement, and assigning any of them are compile errors. Keyed lookups compare a hash of the key first, so finding an item by key stays fast in large Collections.
Error When ----- ---- 5 A key that is not in the Collection (Item, Remove, before or after), or Add given both before and after 9 A position that is not in the Collection 13 A key that is not text - 438 A member other than Add, Remove, Item and Count - 450 The wrong number of arguments + 438 A member other than Add, Remove, Item and Count, or assigning one, through As Object or As Variant + 450 The wrong number of arguments, through As Object or As Variant 457 Add with a key that is already in use
See also: Objects and Class Modules
@@ -3631,6 +4135,21 @@ IniWrite App.Config & "\app.ini", "Display", "FontSConstant Value Description -------- ----- ----------- vbCFText 1 Text, the format the DVX clipboard holds+
The scale units of ScaleMode and of the ScaleX / ScaleY methods, and the values of a Picture object's Type (see Drawing on Forms and PictureBoxes, and LOADPICTURE).
+Constant Value Description + -------- ----- ----------- + vbUser 0 The object's own scale (Scale, ScaleWidth ...) + vbTwips 1 Twips (1440 an inch, 15 a pixel) + vbPoints 2 Points (72 an inch) + vbPixels 3 Pixels (the DVX default ScaleMode) + vbCharacters 4 Characters (120 twips across, 240 down) + vbInches 5 Inches + vbMillimeters 6 Millimetres + vbCentimeters 7 Centimetres + vbHimetric 8 Hundredths of a millimetre (ScaleX / ScaleY only; a Picture's Width and Height) + vbPicTypeNone 0 Picture.Type of an empty picture + vbPicTypeBitmap 1 Picture.Type of a loaded picture
The values VARTYPE returns (see Variants). For an array, VARTYPE adds vbArray to the type of its elements.
Constant Value Kind of value @@ -3641,7 +4160,7 @@ IniWrite App.Config & "\app.ini", "Display", "FontS vbLong 3 Long vbSingle 4 Single vbDouble 5 Double - vbCurrency 6 Currency (Visual Basic only; no DVX value has this type) + vbCurrency 6 Currency vbDate 7 Date vbString 8 String vbObject 9 An object or Nothing @@ -3649,12 +4168,25 @@ IniWrite App.Config & "\app.ini", "Display", "FontS vbBoolean 11 Boolean vbVariant 12 Variant (only in vbArray + vbVariant, an array of Variants) vbDataObject 13 Data object (Visual Basic only; no DVX value has this type) + vbByte 17 Byte vbUserDefinedType 36 A TYPE variable vbArray 8192 Added to the element type for an array
Constant Value Description -------- ----- ----------- vbObjectError -2147221504 Base for your own error numbers, as in Visual Basic: a class module raises ERROR vbObjectError + n, and the handler recovers n with ERR - vbObjectError+
Colors in the form RGB makes (&HBBGGRR, see Color Functions), as in Visual Basic.
+Constant Value Color + -------- ----- ----- + vbBlack &H0 Black + vbRed &HFF Red + vbGreen &HFF00 Green + vbYellow &HFFFF Yellow + vbBlue &HFF0000 Blue + vbMagenta &HFF00FF Magenta + vbCyan &HFFFF00 Cyan + vbWhite &HFFFFFF White
Constant Value Description -------- ----- ----------- @@ -3844,8 +4376,19 @@ End IfToolTipText String R/W Tooltip text shown after the mouse hovers over the control. Empty string or unset = no tooltip. HelpTopic String R/W Help topic opened by F1 while the control has the focus. Empty = the form's HelpTopic. F1 uses the project's help file. DragMode Integer R/W vbManual (0, the default): the program starts a drag with the Drag method. vbAutomatic (1): pressing the left mouse button on the control starts a drag. See Drag and Drop below. - DragIcon String R/W Picture file (BMP, PNG, JPEG or GIF) shown under the mouse while the control is dragged. Empty = a dotted outline the size of the control. + DragIcon Picture R/W Picture shown under the mouse while the control is dragged: a picture file name (BMP, PNG, JPEG or GIF) or a Picture object from LOADPICTURE (Set works too). Reading it gives a Picture object, which stands for the file name where a value is needed. Empty = a dotted outline the size of the control.
Size properties (Width, Height and the Min/Max sizes) are limited to 0..32767.
+As in Visual Basic, a control's name used where a value is needed means its default property: Text1 = "hi" sets Text1.Text, Print Label1 prints Label1.Caption, and If Check1 Then reads Check1.Value. Where an object is expected -- SET, Is, an object parameter, Collection.Add, TypeOf -- the name still means the control itself.
+Control Default property + ------- ---------------- + TextBox, TextArea, ComboBox, DropDown, ListBox, DBGrid Text + Label, Frame, Data Caption + CheckBox, OptionButton, CommandButton Value + HScrollBar, SpinButton, ProgressBar Value + Image, ImageButton, PictureBox Picture + Timer Enabled+
Other controls, forms and menu items have no default property: using one as a value raises error 13.
These events are wired on every control loaded from a .frm file. Controls created dynamically at runtime via code only receive Click, DblClick, Change, GotFocus, and LostFocus; the keyboard, mouse, and scroll events below require the control to be defined in the .frm file.
Event Parameters Description @@ -3872,9 +4415,11 @@ End IfDrag [Action] Starts or ends dragging this control: vbBeginDrag (1, the default) starts, vbEndDrag (2) drops it where the mouse is, 0 cancels the drag without a drop.
Any control can be dragged onto another control or onto a form, as in Visual Basic. While a control is dragged, a dotted outline of it (or its DragIcon picture) follows the mouse, and the controls and forms it passes over get DragOver events. Releasing the mouse button drops it: the control or form under the mouse gets a DragDrop event, with the dragged control as Source. Pressing Esc cancels the drag.
+The mouse pointer shows where a drop can land: over a control or form that has a DragDrop event procedure it is the usual arrow; over one without, and outside the program's forms, it is the "no drop" sign (a circle with a line through it). The outline or DragIcon picture still follows the mouse either way.
With DragMode set to vbAutomatic, pressing the left mouse button on the control starts the drag, and the control gets no Click, MouseDown or MouseUp from the left button. With DragMode vbManual, start the drag yourself, usually in MouseDown. A drag started while the mouse button is up ends at the next click. During a drag no mouse events other than DragOver and DragDrop are raised.
Drop targets only react: nothing is moved or copied for you. Use Source in DragDrop to decide what to do, for example read its Name or Caption, or check what kind of control it is with TypeOf Source Is TextBox (see Operators).
Note that vbCancel is the MsgBox Cancel value (2), which as a Drag action means vbEndDrag; cancel a drag with Drag 0.
+DragIcon takes a file name or a picture loaded with LOADPICTURE: Set Label1.DragIcon = LoadPicture("drag.bmp"). A missing file or one that is not a picture raises its error at LOADPICTURE; a file name that cannot be read when the drag starts shows the outline instead.
' Label1 has DragMode = 1 (vbAutomatic) in the form designer.
Sub List1_DragOver(Source As Control, X As Integer, Y As Integer, State As Integer)
If State = vbEnter Then Label2.Caption = "Drop to add " & Source.Caption
@@ -3894,8 +4439,8 @@ End Sub
Forms and PictureBoxes have Visual Basic's drawing methods. They draw with the same code as the QBasic graphics statements (see Console and Graphics in the language reference), in pixels, with colours as RGB values (RGB(255, 0, 0) or &HFF0000 is red). A PictureBox draws on its picture; a form gets a drawing area the size of its window the first time it draws, placed with its controls like a PictureBox (so draw on a form that has no controls, or use a PictureBox beside them).
-Drawings stay until they are drawn over or cleared: AutoRedraw is accepted, but a drawing always behaves as with AutoRedraw = True. In a form's own code, write Me.Line, Me.Circle and so on: Line, Circle and PSet without an object are the QBasic graphics statements (they draw on the program's screen), and Print without an object is the PRINT statement.
+Forms and PictureBoxes have Visual Basic's drawing methods. They draw with the same code as the QBasic graphics statements (see Console and Graphics in the language reference), in pixels unless ScaleMode or Scale says otherwise, with colours as Visual Basic RGB values, &HBBGGRR (RGB(255, 0, 0) or &HFF is red). A PictureBox draws on its picture; a form gets a drawing area the size of its window the first time it draws, placed with its controls like a PictureBox (so draw on a form that has no controls, or use a PictureBox beside them).
+Drawings stay until they are drawn over or cleared. In a form's own code, write Me.Line, Me.Circle and so on: Line, Circle and PSet without an object are the QBasic graphics statements (they draw on the program's screen), and Print without an object is the PRINT statement.
Method Description ------ ----------- @@ -3905,10 +4450,50 @@ End SubCircle [Step](x, y), radius [, color [, start [, end [, aspect]]]] Draws a circle, ellipse or arc, filled with FillColor when FillStyle is 0 (full circles). Point(x, y) The colour of a pixel, -1 outside the picture. Print items Writes text at CurrentX, CurrentY in ForeColor, with PRINT's ; , SPC and TAB; ends by moving to the next line unless the last item is followed by ; or ,. - PaintPicture picture, x, y [, width, height [, srcX, srcY, srcWidth, srcHeight]] Copies a picture, stretched to width x height: an image file (BMP, PNG, JPEG or GIF), or a PictureBox given by its name as a string or by a Control variable. - TextWidth(text) Width of the text in pixels. - TextHeight(text) Height of a line of text in pixels. -
Without a colour, drawing uses ForeColor. Step makes a point relative to CurrentX, CurrentY, which every drawing method leaves at its last point. A colour or angle out of range stops the program with an error.
+ PaintPicture picture, x, y [, width, height [, srcX, srcY, srcWidth, srcHeight [, opcode]]] Copies a picture, stretched to width x height: an image file (BMP, PNG, JPEG or GIF), a picture from LOADPICTURE, or a PictureBox given by its name as a string or by a Control variable. opcode combines each pixel with the one already there (see Raster Operations). + Scale [(x1, y1)-(x2, y2)] Gives the drawing coordinates of its own: (x1, y1) is the top-left corner and (x2, y2) the bottom-right one (y2 below y1 makes y go up). Scale alone goes back to pixels. + ScaleX(width [, fromScale [, toScale]]) A width in fromScale converted to toScale (see Scales). + ScaleY(height [, fromScale [, toScale]]) A height in fromScale converted to toScale. + TextWidth(text) Width of the text in the scale's units. + TextHeight(text) Height of a line of text in the scale's units. +Without a colour, drawing uses ForeColor. Step makes a point relative to CurrentX, CurrentY, which every drawing method leaves at its last point. Coordinates, radii, CurrentX and CurrentY and PaintPicture's rectangles are in the scale's units (a radius in its horizontal units); DrawWidth is always in pixels. A colour or angle out of range stops the program with an error.
+PaintPicture's opcode says how each copied pixel is combined with the pixel already there (the destination) and with the FillColor (the pattern), as in Visual Basic. Leaving it out copies the picture (vbSrcCopy).
+Constant Value Result + -------- ----- ------ + vbSrcCopy &HCC0020 The picture + vbSrcPaint &HEE0086 Picture OR destination + vbSrcAnd &H8800C6 Picture AND destination + vbSrcInvert &H660046 Picture XOR destination + vbSrcErase &H440328 Picture AND NOT destination + vbNotSrcCopy &H330008 NOT picture + vbNotSrcErase &H1100A6 NOT (picture OR destination) + vbMergeCopy &HC000CA Picture AND pattern + vbMergePaint &HBB0226 NOT picture OR destination + vbPatCopy &HF00021 The pattern + vbPatPaint &HFB0A09 Pattern OR NOT picture OR destination + vbPatInvert &H5A0049 Pattern XOR destination + vbDstInvert &H550009 NOT destination+
Any of the 256 raster operations of Windows can be given by its number; the bits work on the pixel values, so with fewer than 32 bits a pixel the results follow the display's colour format.
+Picture1.PaintPicture Picture2, 0, 0, 32, 32, 0, 0, 32, 32, vbSrcInvert ' XOR the picture in
+Picture1.PaintPicture Picture2, 0, 0, 32, 32, 0, 0, 32, 32, vbSrcInvert ' and out again
+ScaleMode picks the units: 0 user (set by Scale or the Scale properties), 1 twips (1440 an inch, 15 a pixel), 2 points (72 an inch), 3 pixels, 4 characters (120 twips across, 240 down), 5 inches, 6 millimetres, 7 centimetres. Unlike Visual Basic, whose default is twips, DVX starts every form and PictureBox in pixels (ScaleMode 3), and Scale without arguments goes back to pixels. Setting ScaleLeft, ScaleTop, ScaleWidth or ScaleHeight makes a user scale from the current one; ScaleWidth and ScaleHeight may not be 0. With a user scale the units stay fixed to the picture's edges when it is resized; other scale modes keep their unit size.
+The constants vbUser (0), vbTwips (1), vbPoints (2), vbPixels (3), vbCharacters (4), vbInches (5), vbMillimeters (6) and vbCentimeters (7) name the scale modes. The units assume a standard display of 96 pixels an inch (15 twips a pixel), whatever the screen really is, so an inch is always 96 pixels.
+ScaleX and ScaleY convert a width or a height from one scale to another, as in Visual Basic: fromScale is the scale the value is in, vbHimetric (8, hundredths of a millimetre, the unit of a Picture's Width and Height) when it is left out; toScale the scale wanted, the object's ScaleMode when left out. vbUser means the object's current scale. Visual Basic's vbContainerPosition and vbContainerSize are not available; any other scale number stops the program with an error.
+Picture1.Scale (0, 100)-(100, 0) ' 0..100 across, y going up
+Picture1.Line (0, 0)-(100, 100), RGB(255, 0, 0)
+Picture1.ScaleMode = 7 ' centimetres
+Picture1.Circle (2, 2), 1
+
+Dim pic As Object
+Set pic = LoadPicture("logo.bmp")
+w = Me.ScaleX(pic.Width, vbHimetric, vbPixels) ' the picture's width in pixels
+A form or PictureBox fires its Paint event when it is shown, when the form is resized, when a PictureBox changes size (its Width or Height set to a new value, or its Resize method) while its form is showing, and when the program calls its Refresh method, so a program can draw everything in Paint as in Visual Basic. Unlike Visual Basic, DVX keeps what was drawn even with AutoRedraw False (the default), so drawing outside Paint also stays on the screen. With AutoRedraw = True, Paint does not fire. A program that sets AutoRedraw = False itself gets Visual Basic's behaviour exactly: the drawing is cleared to BackColor before each Paint.
+Sub Picture1_Paint ()
+ Picture1.Circle (50, 50), 40, RGB(0, 128, 0)
+End Sub
Property Type Default Description -------- ---- ------- ----------- @@ -3917,11 +4502,14 @@ End SubCurrentX Single 0 Where the next drawing or printing starts. CurrentY Single 0 DrawWidth Integer 1 Pen width in pixels. - FillStyle Integer 1 0 fills boxes and circles with FillColor; 1 (the default) leaves them empty. Other VB fill patterns draw as 1. + FillStyle Integer 1 How boxes (B) and circles are filled with FillColor: 0 solid, 1 (the default) not at all, 2 horizontal lines, 3 vertical lines, 4 upward diagonals, 5 downward diagonals, 6 a cross hatch, 7 a diagonal cross hatch (the lines 8 pixels apart, the gaps left as they are). Other values are an error. BF fills solid in the line's colour. FillColor Long &H000000 Colour boxes and circles are filled with. - AutoRedraw Boolean False Accepted; drawings always persist. - ScaleWidth Long (size) Width of the drawing in pixels (read-only). - ScaleHeight Long (size) Height of the drawing in pixels (read-only). + AutoRedraw Boolean False True: no Paint events (see above). + ScaleMode Integer 3 Units of the coordinates (see Scales). + ScaleLeft Single 0 Coordinate of the left edge. + ScaleTop Single 0 Coordinate of the top edge. + ScaleWidth Single (size) Width of the drawing in the scale's units. + ScaleHeight Single (size) Height of the drawing in the scale's units.
Sub Command1_Click ()
Picture1.Resize 200, 150
@@ -4208,9 +4796,10 @@ End Sub
Resize (none) Fires when the window is resized by the user.
Activate (none) Fires when the window gains focus.
Deactivate (none) Fires when the window loses focus.
+ Paint (none) Fires when the form is shown or resized and on Refresh, unless AutoRedraw is True (see Drawing on Forms and PictureBoxes).
DragDrop Source As Control, X As Integer, Y As Integer Fires when a dragged control is dropped on an empty part of the form. X and Y are where, in the form.
DragOver Source As Control, X As Integer, Y As Integer, State As Integer Fires while a dragged control moves over an empty part of the form. State is vbEnter, vbLeave or vbOver.
-See Drag and Drop under Common Properties, Events, and Methods.
+See Drag and Drop under Common Properties, Events, and Methods. While a control is dragged over a form with no DragDrop event procedure, the mouse pointer shows the "no drop" sign.
Statement Description ------------------ ------------------------------------------- @@ -4220,7 +4809,7 @@ End SubFormName.Show 1 Show as modal dialog (blocks until closed). FormName.Hide Hide the form without unloading it.
Forms have Visual Basic's drawing methods and properties (Cls, PSet, Line, Circle, Point, Print, PaintPicture, CurrentX, CurrentY, ForeColor, BackColor, DrawWidth, FillColor, FillStyle): see Drawing on Forms and PictureBoxes. In the form's own code use Me.Line, Me.Circle and so on.
+Forms have Visual Basic's drawing methods and properties (Cls, PSet, Line, Circle, Point, Print, PaintPicture, Scale, CurrentX, CurrentY, ForeColor, BackColor, DrawWidth, FillColor, FillStyle, AutoRedraw, ScaleMode, ScaleLeft, ScaleTop, ScaleWidth, ScaleHeight): see Drawing on Forms and PictureBoxes. In the form's own code use Me.Line, Me.Circle and so on.
Sub Form_Load ()
Form1.Caption = "Hello World"
@@ -4370,7 +4959,7 @@ End Sub
VB Equivalent: PictureBox -- DVX Widget: canvas | Name Prefix: Picture
A drawing surface (canvas). Supports drawing lines, rectangles, circles, text, and individual pixels. Can load BMP, PNG, JPEG and GIF images and save BMP or PNG images. The default canvas size is 64x64 pixels.
-Colors are specified as 0x00RRGGBB integers (e.g. &HFF0000 for red).
+Colors are Visual Basic RGB values, &HBBGGRR, as RGB() makes them (RGB(255, 0, 0) or &HFF is red).
Property Type Description -------- ---- ----------- @@ -4399,12 +4988,12 @@ End SubPicture1.Clear &HFFFFFF Picture1.SetPenSize 2 Picture1.DrawRect 10, 10, 180, 130, &H000000 -Picture1.FillCircle 100, 75, 40, &HFF0000 +Picture1.FillCircle 100, 75, 40, RGB(255, 0, 0) Picture1.DrawText 60, 130, "Hello!" If Picture1.Save("output.bmp") <> 0 Then MsgBox "Could not save output.bmp" End If -
A PictureBox also has Visual Basic's drawing methods and properties (Cls, PSet, Line, Circle, Point, Print, PaintPicture, CurrentX, CurrentY, ForeColor, BackColor, DrawWidth, FillColor, FillStyle): see Drawing on Forms and PictureBoxes.
+A PictureBox also has Visual Basic's drawing methods and properties (Cls, PSet, Line, Circle, Point, Print, PaintPicture, Scale, CurrentX, CurrentY, ForeColor, BackColor, DrawWidth, FillColor, FillStyle, AutoRedraw, ScaleMode, ScaleLeft, ScaleTop, ScaleWidth, ScaleHeight) and a Paint event: see Drawing on Forms and PictureBoxes.
Property Type Description ----------- ------- ------------------------------------------- - Picture String Path to an image file to load. Reads back the file the image came from. Setting it to "" clears the image. A file that cannot be loaded leaves the image and Picture unchanged. + Picture Picture Path to an image file to load (or a Picture object from LOADPICTURE). Reads back as a Picture object for the file the image came from, which stands for the file name where a value is needed. Setting it to "" clears the image. A file that cannot be loaded leaves the image and Picture unchanged. ImageWidth Integer Width of the loaded image in pixels (read-only). ImageHeight Integer Height of the loaded image in pixels (read-only).
No type-specific methods or events.
@@ -4633,7 +5222,7 @@ End SubProperty Type Description ----------- ------- ------------------------------------------- - Picture String Path to an image file to load. Reads back the file the image came from. Setting it to "" clears the image. A file that cannot be loaded leaves the image and Picture unchanged. A disabled Image is drawn grayed (its transparent color stays transparent). + Picture Picture Path to an image file to load (or a Picture object from LOADPICTURE). Reads back as a Picture object for the file the image came from, which stands for the file name where a value is needed. Setting it to "" clears the image. A file that cannot be loaded leaves the image and Picture unchanged. A disabled Image is drawn grayed (its transparent color stays transparent). Stretch Boolean When True, an image larger than the control is scaled down to fit, keeping its proportions; a smaller image stays at natural size. When False, the image is shown at natural size. ImageWidth Integer Width of the loaded image in pixels (read-only). ImageHeight Integer Height of the loaded image in pixels (read-only).diff --git a/docs/dvx_system_reference.html b/docs/dvx_system_reference.html index 12e2e1e0..e42ef113 100644 --- a/docs/dvx_system_reference.html +++ b/docs/dvx_system_reference.html @@ -404,6 +404,7 @@ img { max-width: 100%; }
Static const array of CursorT structs, indexed by CURSOR_xxx constants. Each entry includes the AND mask, XOR data (both as 16-element uint16_t arrays), dimensions (16x16), and hot spot coordinates.
@@ -4409,7 +4413,10 @@ bool dvxAppIsHung(const AppContextT *ctx, int32_t appId);Show a picture centered on the mouse pointer. pixels are in the display's native format, as dvxLoadImage or dvxLoadImageAlpha return them, and must stay valid until the feedback is hidden or replaced. With hasKey, pixels of keyColor are transparent (pass dvxLoadImageAlpha's outputs).
void dvxDragFeedbackHide(AppContextT *ctx);
-Remove the outline or picture.
+Remove the outline or picture. The usual cursor shapes come back (see dvxDragCursorSet).
+void dvxDragCursorSet(AppContextT *ctx, int32_t cursorId);
+Choose the mouse cursor shown while the outline or picture is up: CURSOR_NO_DROP where a drop cannot land, CURSOR_ARROW (the default) where it can. The program calls it as the mouse moves, since only it knows its drop targets. While drag feedback is shown this cursor replaces the usual shapes (resize arrows, a widget's own cursor); hiding the feedback puts them back and resets the choice to CURSOR_ARROW. Values outside the cursor table are ignored.
uint8_t *dvxResLoadIcon(AppContextT *ctx, const char *dxePath, const char *resName, int32_t *outW, int32_t *outH, int32_t *outPitch);
@@ -4744,6 +4751,13 @@ static const ButtonApiT sApi = {
minSize sizeof the API type the caller will use: sizeof(ButtonApiT) for a typed accessor, sizeof(WgtApiPrefixT) when only create is called
Returns: Pointer to the API struct, or NULL if not found or refused. Per-widget headers wrap the call in a typed accessor (dvxButtonApi() and friends).
static inline uint32_t wgtBasicColor(uint32_t color);
+Converts a colour between DVX's 0x00RRGGBB layout (WidgetT fgColor / bgColor, packColor's channels, the colour dialog) and DVX BASIC's Visual Basic layout &HBBGGRR (RGB(), QBColor, ForeColor / BackColor, drawing-method colour arguments) by swapping the red and blue bytes; the top byte is kept, so the function is its own inverse. A widget's BASIC methods call it once where a BASIC colour comes in and once where one goes back out.
+Parameter Description + --------- ----------- + color A colour in either layout+
Returns: The colour in the other layout.
void wgtRegisterIface(const char *name, const WgtIfaceT *iface);
Register an interface descriptor for a widget type. Used by the BASIC form runtime and IDE for generic property/method dispatch.
@@ -5109,7 +5123,7 @@ int32_t pick = dvxButtonBox(ctx, "Copy", "The disk is full." --------- ----------- ctx Application context title Window title (NULL: "Color") - rgbInOut In: the starting color; out: the chosen color. Both 0x00RRGGBB, the layout of DVX BASIC's RGB() + rgbInOut In: the starting color; out: the chosen color. Both 0x00RRGGBB (DVX BASIC's colours are &HBBGGRR; wgtBasicColor converts between the two)Returns: true if OK was chosen, false if cancelled or closed (then *rgbInOut is unchanged).
Macros for the 0x00RRGGBB layout: DVX_RGB(r, g, b) builds a color from three 0-255 channels, and DVX_RGB_RED(rgb), DVX_RGB_GREEN(rgb) and DVX_RGB_BLUE(rgb) take one apart.
uint32_t rgb = DVX_RGB(r, g, b);
@@ -6047,11 +6061,15 @@ audioFmFree(audio, v); // key off, back to everyone
+ BAS_TYPE_REF 9 ref Reference (ByRef argument): ref.target is the variable, element or field it names; ref.owner keeps that storage alive.
+A reference only ever lives in a call's parameters (a ByRef parameter, a ParamArray element). Read through it with basValDeref, which returns the value a reference names (or the value itself). refKind says what ref.owner is: BAS_REF_SLOT (none: a global, local or form variable), BAS_REF_ARRAY (the array, counted and locked so REDIM / ERASE raise error 10), BAS_REF_UDT (the TYPE instance holding the field), BAS_REF_OBJECT (the class instance holding the member variable) or BAS_REF_CELL (a BasRefCellT holding both the locked array and the TYPE instance, for a field of an array element). refType is the target's declared type (stores convert to it) and refLen the length of a STRING * n target. basValCopy and basValRelease count the owner.
Tagged union holding any BASIC value.
struct BasValueTag {
- uint8_t type; // BAS_TYPE_*
+ uint8_t type; // BAS_TYPE_*
+ uint8_t refType : 5; // BAS_TYPE_REF: the target's declared type
+ uint8_t refKind : 3; // BAS_TYPE_REF: BasRefKindE
+ uint16_t refLen; // BAS_TYPE_REF: STRING * n length (0 = none)
union {
int16_t intVal;
int32_t longVal;
@@ -6062,9 +6080,15 @@ audioFmFree(audio, v); // key off, back to everyone
+};
+
+BasValueT *basValDeref(const BasValueT *v); // inline: what a reference names, else v
BasValueT basValInteger(int16_t v);
BasValueT basValLong(int32_t v);
@@ -6296,7 +6320,7 @@ bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr,
-------- -----------
basVmCallSub Call a SUB with no arguments.
basVmCallSubWithArgs Call a SUB with arguments pushed onto the stack frame.
- basVmCallSubWithArgsOut Call a SUB and read back modified argument values after it returns.
+ basVmCallSubWithArgsOut Call a SUB whose first outCount parameters are references to outArgs (set to the arguments first; each must hold a valid value): what the SUB stores in them is there when it returns, also after an error.
All three push a call frame, execute until the SUB returns, then restore the previous execution state. Return true on normal completion, false on error or if the VM is not idle.
void basVmSetBreakpoints(BasVmT *vm,
@@ -7336,6 +7360,7 @@ win->onRawKey = gameRawKey; // each key going down and up
Ctrl+Esc Open the Task Manager
Alt+F4 Close the window (its onClose)
Every other key, F1, F10, Alt+letter and the accelerators included, goes to onKey instead of opening help or menus. The game decides what F1 does (the template opens its help with shellOpenHelp); its menus still work with the mouse. onRawKey gets a RawKeyEventT for every key going down or up: the set-1 scancode with an extended flag for the grey keys, down or up, whether it is a typematic repeat, and the modifiers. Shift, Ctrl and Alt are keys of their own there, left and right apart.
+The key that completes one of the three (Esc with Ctrl held, Tab or F4 with Alt held) never reaches the game: not in onRawKey, not in the held-key table, not its repeats and not its release. The modifier itself does, because when Ctrl or Alt goes down nobody can tell yet what will follow. A game that treats any key as "go on" therefore still sees the Ctrl or Alt of Ctrl+Esc or Alt+Tab.
For "is it held right now" use gameKeyIsDown(win, rawCode) with a raw key code (KEY_RAW_UP, KEY_RAW_LCTRL, or KEY_RAW_CODE(scan, extended) for any key); it is true only while win has the focus. platformKeyIsDown and platformKeyStateGet read the table itself. When the focus moves away, the game gets a release for every key still held and the table starts over, so no key stays stuck down. A key already held when the window gets the focus is seen once it repeats.
Every music plays through one audio context of the library's own; audio.lib mixes all streams, so modules and software MIDI of any number of programs play together. FM voices are shared with every program's tones and FM music; the MPU-401 port serves one music. When a program ends, its musics stop and are freed and their FM voices and the port are given back; the others go on.
src/include/basic/music.bas (DECLARE LIBRARY "music"): MusicLoad(fileName) and MusicMidiOpen() return a music number (0 on failure: MusicError$ says why); MusicPlay(music, loopIt), MusicStop, MusicPause, MusicResume, MusicPlaying, MusicVolume(music, percent), MusicPosition, MusicLength, MusicSeek(music, ms), MusicFree; live MIDI MusicNoteOn(music, channel, note, velocity), MusicNoteOff, MusicProgram, MusicControl, MusicPitchBend with channels 1 to 16 (10 for drums). A number only works for the program that got it.
-The BASIC runtime itself calls musicBasBeep, musicBasSound(hz, ticks), musicBasPlay(macro), musicBasPlayCount and musicBasBusy for the statements BEEP, SOUND, PLAY and the PLAY(n) function; each BASIC program has its own macro queue. SOUND's duration is in clock ticks (18.2 a second) and 0 stops the queue; BEEP is 800 Hz for a quarter second.
+The BASIC runtime itself calls musicBasBeep, musicBasSound(hz, ticks), musicBasPlay(macro), musicBasPlayCount and musicBasBusy for the statements BEEP, SOUND, PLAY and the PLAY(n) function; each BASIC program has its own macro queue. QBasic's X command and "=" numbers (given with VARPTR$) are filled in by the BASIC runtime before musicBasPlay is called, so the macro language itself has neither. SOUND's duration is in clock ticks (18.2 a second) and 0 stops the queue; BEEP is 800 Hz for a quarter second.
libxmp-lite (Extended Module Player Lite), (C) 1996-2026 Claudio Matsuoka and Hipolito Carraro Jr, MIT licence:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -9360,17 +9385,17 @@ btn->onClick = onOkClicked;Method Description ------ ----------- - Clear color% Fill the entire canvas with a 0x00RRGGBB color. + Clear color% Fill the entire canvas with a color (&HBBGGRR, as RGB() makes it). DrawLine x0%, y0%, x1%, y1%, color% Draw a line between two points. DrawRect x%, y%, w%, h%, color% Draw a rectangle outline. DrawText x%, y%, text$ Draw text at the given position. FillCircle cx%, cy%, radius%, color% Fill a circle. FillRect x%, y%, w%, h%, color% Fill a rectangle. - GetPixel(x%, y%) Returns the 0x00RRGGBB color at a pixel. + GetPixel(x%, y%) Returns the color at a pixel (&HBBGGRR). Load(path$) Load a BMP, PNG, JPEG or GIF image (the canvas takes its size). Returns 0 on success, -1 on error (WGT_SIG_RET_INT_STR). Resize w%, h% Resize the canvas (the drawing is cleared to white). Save(path$) Save the canvas: BMP when path$ ends in .bmp, PNG otherwise. Returns 0 on success, -1 on error (WGT_SIG_RET_INT_STR). - SetPenColor color% Set the drawing color (0x00RRGGBB). + SetPenColor color% Set the drawing color (&HBBGGRR). SetPenSize size% Set the pen size in pixels (1 to 255). SetPixel x%, y%, color% Set a single pixel.
Property Type Access Description -------- ---- ------ ----------- - Picture String Read/Write Setting writes a file path (loads image). Reading returns the path of the loaded file (also set by wgtImageButtonFromFile); wgtImageButtonSetData clears it. Setting "" clears the image and the path; a file that cannot be loaded leaves both unchanged. + Picture String Read/Write Setting writes a file path (loads image). Reading returns the path of the loaded file (also set by wgtImageButtonFromFile); in BASIC the path comes as a Picture object, which stands for the path where a value is needed (see LOADPICTURE); wgtImageButtonSetData clears it. Setting "" clears the image and the path; a file that cannot be loaded leaves both unchanged. ImageWidth Integer Read-only Width of the currently loaded image in pixels. ImageHeight Integer Read-only Height of the currently loaded image in pixels.
Property Type Access Description -------- ---- ------ ----------- - Picture String Read/Write Setting writes a file path (loads image). Reading returns the path of the loaded file (also set by wgtImageFromFile); wgtImageSetData clears it. Setting "" clears the image and the path; a file that cannot be loaded leaves both unchanged. + Picture String Read/Write Setting writes a file path (loads image). Reading returns the path of the loaded file (also set by wgtImageFromFile); in BASIC the path comes as a Picture object, which stands for the path where a value is needed (see LOADPICTURE); wgtImageSetData clears it. Setting "" clears the image and the path; a file that cannot be loaded leaves both unchanged. Stretch Boolean Read/Write When true, an image larger than the widget is scaled down to fit (aspect preserved, nearest neighbour); a smaller image stays at natural size. When false, shown at natural size. Changing it relayouts the widget. ImageWidth Integer Read-only Width of the currently loaded image in pixels. ImageHeight Integer Read-only Height of the currently loaded image in pixels.diff --git a/screenshots/86box/hang-ended.png b/screenshots/86box/hang-ended.png index 117141f4..c60d4df3 100644 --- a/screenshots/86box/hang-ended.png +++ b/screenshots/86box/hang-ended.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:219af82c920e15e77699f3066ba3f01e879a5bf1b74cd44df479e5843d0704a0 -size 14950 +oid sha256:643242f2675830fea0534491de621787206cba57b87b55b2a2a5311f2f618153 +size 14943 diff --git a/screenshots/86box/hang-taskmgr.png b/screenshots/86box/hang-taskmgr.png index fdfcc6b7..0629d2be 100644 --- a/screenshots/86box/hang-taskmgr.png +++ b/screenshots/86box/hang-taskmgr.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d6904e5ed0879155215fea1b7767e83fbfa4095517274d26ce0a1c612375a888 -size 15948 +oid sha256:97017a5b3ba488a78d44a87ad1afdd778dcf7adde5144486a3824e2d4d644da0 +size 15950 diff --git a/screenshots/86box/install-su-copy.png b/screenshots/86box/install-su-copy.png new file mode 100644 index 00000000..db2a349a --- /dev/null +++ b/screenshots/86box/install-su-copy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ce985d712ed7704d64256f877117d038d196ff7ecdd53facaf70dcd45ae5c1d +size 11206 diff --git a/screenshots/86box/install-su-done.png b/screenshots/86box/install-su-done.png new file mode 100644 index 00000000..ae6ab8cf --- /dev/null +++ b/screenshots/86box/install-su-done.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:949e00a5e0b0091f862a6649b69c3a6bc884e2abd0bdcbc46cb5f84668fa8952 +size 12102 diff --git a/screenshots/86box/install-su-fold.png b/screenshots/86box/install-su-fold.png new file mode 100644 index 00000000..3f10248f --- /dev/null +++ b/screenshots/86box/install-su-fold.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c2de1fb480d7b272907fe5da333b01c8958ccf71421619c3eea483baba586b3 +size 14287 diff --git a/screenshots/86box/install-su-lic.png b/screenshots/86box/install-su-lic.png new file mode 100644 index 00000000..72644a79 --- /dev/null +++ b/screenshots/86box/install-su-lic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62f1df23ec94c8e5da49f35fb68356282fdfe4a1999285940f0610161f085f28 +size 16569 diff --git a/screenshots/86box/install-su-unin.png b/screenshots/86box/install-su-unin.png new file mode 100644 index 00000000..5a958b05 --- /dev/null +++ b/screenshots/86box/install-su-unin.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:017a9069fe14f6d1f7e61141206803f4c9ec248357db714984a21c60b9e04cd8 +size 12775 diff --git a/screenshots/86box/scrpage-cga1.png b/screenshots/86box/scrpage-cga1.png new file mode 100644 index 00000000..ecb720f0 --- /dev/null +++ b/screenshots/86box/scrpage-cga1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87787977cb6c83b8f543723bdbe403727ce1e2647bcb6fb1bd0490e70f29ff20 +size 17529 diff --git a/screenshots/86box/scrpage-cga2.png b/screenshots/86box/scrpage-cga2.png new file mode 100644 index 00000000..d9fc21b4 --- /dev/null +++ b/screenshots/86box/scrpage-cga2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d501381f4877e599ff35b3e2c33879d5cb1e0ae4af90448142e27e0f64d4058 +size 18986 diff --git a/screenshots/86box/scrpage-full2.png b/screenshots/86box/scrpage-full2.png new file mode 100644 index 00000000..31b98f67 --- /dev/null +++ b/screenshots/86box/scrpage-full2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb683a1bf9ef95a6c9183cad24d5fda1ee2932e89815038f3b4af9e711cbc4c7 +size 10106 diff --git a/screenshots/86box/scrpage-page0.png b/screenshots/86box/scrpage-page0.png new file mode 100644 index 00000000..1fa1c92e --- /dev/null +++ b/screenshots/86box/scrpage-page0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f17df9b127e2f07c6decb6481d9d8785e4d2aeb309841376a36c0f5018e9fba9 +size 16216 diff --git a/screenshots/86box/scrpage-pcopy.png b/screenshots/86box/scrpage-pcopy.png new file mode 100644 index 00000000..d3f0decf --- /dev/null +++ b/screenshots/86box/scrpage-pcopy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8213a7780ee2f1d7316d1f10c5dd0e1b9028581ff02e92f80ac8f14cda56d24b +size 17737 diff --git a/screenshots/86box/scrpage-text40.png b/screenshots/86box/scrpage-text40.png new file mode 100644 index 00000000..a09a695b --- /dev/null +++ b/screenshots/86box/scrpage-text40.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a02b084025abc1717365dabbdafe5a8674218f53f6d07cfa66bcef28862205ed +size 19649 diff --git a/screenshots/86box/upgrade-su-copy.png b/screenshots/86box/upgrade-su-copy.png new file mode 100644 index 00000000..db2a349a --- /dev/null +++ b/screenshots/86box/upgrade-su-copy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ce985d712ed7704d64256f877117d038d196ff7ecdd53facaf70dcd45ae5c1d +size 11206 diff --git a/screenshots/86box/upgrade-su-done.png b/screenshots/86box/upgrade-su-done.png new file mode 100644 index 00000000..ae6ab8cf --- /dev/null +++ b/screenshots/86box/upgrade-su-done.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:949e00a5e0b0091f862a6649b69c3a6bc884e2abd0bdcbc46cb5f84668fa8952 +size 12102 diff --git a/screenshots/86box/upgrade-su-fold.png b/screenshots/86box/upgrade-su-fold.png new file mode 100644 index 00000000..3f10248f --- /dev/null +++ b/screenshots/86box/upgrade-su-fold.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c2de1fb480d7b272907fe5da333b01c8958ccf71421619c3eea483baba586b3 +size 14287 diff --git a/screenshots/86box/upgrade-su-lic.png b/screenshots/86box/upgrade-su-lic.png new file mode 100644 index 00000000..72644a79 --- /dev/null +++ b/screenshots/86box/upgrade-su-lic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62f1df23ec94c8e5da49f35fb68356282fdfe4a1999285940f0610161f085f28 +size 16569 diff --git a/screenshots/86box/upgrade-su-unin.png b/screenshots/86box/upgrade-su-unin.png new file mode 100644 index 00000000..5a958b05 --- /dev/null +++ b/screenshots/86box/upgrade-su-unin.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:017a9069fe14f6d1f7e61141206803f4c9ec248357db714984a21c60b9e04cd8 +size 12775 diff --git a/screenshots/86box/upgrade-su-upgr.png b/screenshots/86box/upgrade-su-upgr.png new file mode 100644 index 00000000..dd918110 --- /dev/null +++ b/screenshots/86box/upgrade-su-upgr.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f69a18ad4aaef9c09fe27f220d522f35d74b9c3fc059d0e7561e85bb412edd5 +size 15726 diff --git a/screenshots/dosbox/hang-ended.png b/screenshots/dosbox/hang-ended.png index 9bd9e559..f67d933d 100644 --- a/screenshots/dosbox/hang-ended.png +++ b/screenshots/dosbox/hang-ended.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dc84216c8c0d465ed307386200def6ec543f5576e6da27486fabe3fd6f70c882 -size 14919 +oid sha256:2562044174c6bbea89cfc399511da6ff363bcf96b12b97b86f01b69ff4dedde9 +size 14940 diff --git a/screenshots/dosbox/hang-taskmgr.png b/screenshots/dosbox/hang-taskmgr.png index 55931b84..47330b2b 100644 --- a/screenshots/dosbox/hang-taskmgr.png +++ b/screenshots/dosbox/hang-taskmgr.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:180d4b700e6f94d84856f4419304fad93c29d1844df48c1c6a9fd32b64e478bd -size 15967 +oid sha256:213863151003ceb8af9545b82011a3127289484e6252eed6b587786589c55403 +size 15939 diff --git a/screenshots/dosbox/install-in-desk.png b/screenshots/dosbox/install-in-desk.png new file mode 100644 index 00000000..3686b74f --- /dev/null +++ b/screenshots/dosbox/install-in-desk.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9a079ee9e34ce6c7217d1fed22a0e937852655caec1e275d925deddc3001e38 +size 18154 diff --git a/screenshots/dosbox/install-in-welc.png b/screenshots/dosbox/install-in-welc.png new file mode 100644 index 00000000..4516c8b0 --- /dev/null +++ b/screenshots/dosbox/install-in-welc.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21bb78b0da44f365e9918bc34a2b1a0425ae4ca96667959e8be3f4b754f547e5 +size 17010 diff --git a/screenshots/dosbox/install-su-copy.png b/screenshots/dosbox/install-su-copy.png new file mode 100644 index 00000000..ec552cea --- /dev/null +++ b/screenshots/dosbox/install-su-copy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28ead113ab376e2ff6c197beacf06d084b2b09842612b7f974b8093d93267cfe +size 11358 diff --git a/screenshots/dosbox/install-su-disk.png b/screenshots/dosbox/install-su-disk.png new file mode 100644 index 00000000..e8a4c386 --- /dev/null +++ b/screenshots/dosbox/install-su-disk.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a4acd0f91618626865fcd83b05f2819e3af4883c7245c977df6fbcc8b47aa69 +size 13322 diff --git a/screenshots/dosbox/install-su-done.png b/screenshots/dosbox/install-su-done.png new file mode 100644 index 00000000..ae6ab8cf --- /dev/null +++ b/screenshots/dosbox/install-su-done.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:949e00a5e0b0091f862a6649b69c3a6bc884e2abd0bdcbc46cb5f84668fa8952 +size 12102 diff --git a/screenshots/dosbox/install-su-fold.png b/screenshots/dosbox/install-su-fold.png new file mode 100644 index 00000000..3f10248f --- /dev/null +++ b/screenshots/dosbox/install-su-fold.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c2de1fb480d7b272907fe5da333b01c8958ccf71421619c3eea483baba586b3 +size 14287 diff --git a/screenshots/dosbox/install-su-lic.png b/screenshots/dosbox/install-su-lic.png new file mode 100644 index 00000000..72644a79 --- /dev/null +++ b/screenshots/dosbox/install-su-lic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62f1df23ec94c8e5da49f35fb68356282fdfe4a1999285940f0610161f085f28 +size 16569 diff --git a/screenshots/dosbox/install-su-unin.png b/screenshots/dosbox/install-su-unin.png new file mode 100644 index 00000000..5a958b05 --- /dev/null +++ b/screenshots/dosbox/install-su-unin.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:017a9069fe14f6d1f7e61141206803f4c9ec248357db714984a21c60b9e04cd8 +size 12775 diff --git a/screenshots/dosbox/install-su-wrong.png b/screenshots/dosbox/install-su-wrong.png new file mode 100644 index 00000000..d31b6033 --- /dev/null +++ b/screenshots/dosbox/install-su-wrong.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ec1d513bd8ef27a667134b2ba36a37a3bf201f518899cb1c320b4a6bb82f2ed +size 13856 diff --git a/screenshots/dosbox/scrpage-cga1.png b/screenshots/dosbox/scrpage-cga1.png new file mode 100644 index 00000000..ecb720f0 --- /dev/null +++ b/screenshots/dosbox/scrpage-cga1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87787977cb6c83b8f543723bdbe403727ce1e2647bcb6fb1bd0490e70f29ff20 +size 17529 diff --git a/screenshots/dosbox/scrpage-cga2.png b/screenshots/dosbox/scrpage-cga2.png new file mode 100644 index 00000000..d9fc21b4 --- /dev/null +++ b/screenshots/dosbox/scrpage-cga2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d501381f4877e599ff35b3e2c33879d5cb1e0ae4af90448142e27e0f64d4058 +size 18986 diff --git a/screenshots/dosbox/scrpage-full2.png b/screenshots/dosbox/scrpage-full2.png new file mode 100644 index 00000000..31b98f67 --- /dev/null +++ b/screenshots/dosbox/scrpage-full2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb683a1bf9ef95a6c9183cad24d5fda1ee2932e89815038f3b4af9e711cbc4c7 +size 10106 diff --git a/screenshots/dosbox/scrpage-page0.png b/screenshots/dosbox/scrpage-page0.png new file mode 100644 index 00000000..1fa1c92e --- /dev/null +++ b/screenshots/dosbox/scrpage-page0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f17df9b127e2f07c6decb6481d9d8785e4d2aeb309841376a36c0f5018e9fba9 +size 16216 diff --git a/screenshots/dosbox/scrpage-pcopy.png b/screenshots/dosbox/scrpage-pcopy.png new file mode 100644 index 00000000..d3f0decf --- /dev/null +++ b/screenshots/dosbox/scrpage-pcopy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8213a7780ee2f1d7316d1f10c5dd0e1b9028581ff02e92f80ac8f14cda56d24b +size 17737 diff --git a/screenshots/dosbox/scrpage-text40.png b/screenshots/dosbox/scrpage-text40.png new file mode 100644 index 00000000..a09a695b --- /dev/null +++ b/screenshots/dosbox/scrpage-text40.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a02b084025abc1717365dabbdafe5a8674218f53f6d07cfa66bcef28862205ed +size 19649 diff --git a/screenshots/dosbox/upgrade-su-copy.png b/screenshots/dosbox/upgrade-su-copy.png new file mode 100644 index 00000000..db2a349a --- /dev/null +++ b/screenshots/dosbox/upgrade-su-copy.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ce985d712ed7704d64256f877117d038d196ff7ecdd53facaf70dcd45ae5c1d +size 11206 diff --git a/screenshots/dosbox/upgrade-su-done.png b/screenshots/dosbox/upgrade-su-done.png new file mode 100644 index 00000000..ae6ab8cf --- /dev/null +++ b/screenshots/dosbox/upgrade-su-done.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:949e00a5e0b0091f862a6649b69c3a6bc884e2abd0bdcbc46cb5f84668fa8952 +size 12102 diff --git a/screenshots/dosbox/upgrade-su-fold.png b/screenshots/dosbox/upgrade-su-fold.png new file mode 100644 index 00000000..3f10248f --- /dev/null +++ b/screenshots/dosbox/upgrade-su-fold.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c2de1fb480d7b272907fe5da333b01c8958ccf71421619c3eea483baba586b3 +size 14287 diff --git a/screenshots/dosbox/upgrade-su-lic.png b/screenshots/dosbox/upgrade-su-lic.png new file mode 100644 index 00000000..72644a79 --- /dev/null +++ b/screenshots/dosbox/upgrade-su-lic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62f1df23ec94c8e5da49f35fb68356282fdfe4a1999285940f0610161f085f28 +size 16569 diff --git a/screenshots/dosbox/upgrade-su-unin.png b/screenshots/dosbox/upgrade-su-unin.png new file mode 100644 index 00000000..5a958b05 --- /dev/null +++ b/screenshots/dosbox/upgrade-su-unin.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:017a9069fe14f6d1f7e61141206803f4c9ec248357db714984a21c60b9e04cd8 +size 12775 diff --git a/screenshots/dosbox/upgrade-su-upgr.png b/screenshots/dosbox/upgrade-su-upgr.png new file mode 100644 index 00000000..dd918110 --- /dev/null +++ b/screenshots/dosbox/upgrade-su-upgr.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f69a18ad4aaef9c09fe27f220d522f35d74b9c3fc059d0e7561e85bb412edd5 +size 15726 diff --git a/screenshots/masks/install-su-disk.mask b/screenshots/masks/install-su-disk.mask new file mode 100644 index 00000000..98bb14cc --- /dev/null +++ b/screenshots/masks/install-su-disk.mask @@ -0,0 +1,2 @@ +# Status line and progress bar behind the prompt: the file being unpacked when the part ran out and how far it got (both change with the package) +72 106 496 48 diff --git a/screenshots/masks/install-su-fold.mask b/screenshots/masks/install-su-fold.mask new file mode 100644 index 00000000..cc2f0e61 --- /dev/null +++ b/screenshots/masks/install-su-fold.mask @@ -0,0 +1,4 @@ +# Setup's "DVX needs about N MB" figure (grows with the package) and the rest of that line +202 83 176 16 +# Blinking text caret after C:\DVX in the folder box +123 131 3 18 diff --git a/screenshots/masks/install-su-wrong.mask b/screenshots/masks/install-su-wrong.mask new file mode 100644 index 00000000..98bb14cc --- /dev/null +++ b/screenshots/masks/install-su-wrong.mask @@ -0,0 +1,2 @@ +# Status line and progress bar behind the prompt: the file being unpacked when the part ran out and how far it got (both change with the package) +72 106 496 48 diff --git a/screenshots/masks/upgrade-su-fold.mask b/screenshots/masks/upgrade-su-fold.mask new file mode 100644 index 00000000..cc2f0e61 --- /dev/null +++ b/screenshots/masks/upgrade-su-fold.mask @@ -0,0 +1,4 @@ +# Setup's "DVX needs about N MB" figure (grows with the package) and the rest of that line +202 83 176 16 +# Blinking text caret after C:\DVX in the folder box +123 131 3 18 diff --git a/src/apps/kpunch/dvxbasic/Makefile b/src/apps/kpunch/dvxbasic/Makefile index fc5056d3..aea6f1c2 100644 --- a/src/apps/kpunch/dvxbasic/Makefile +++ b/src/apps/kpunch/dvxbasic/Makefile @@ -58,7 +58,7 @@ SYSTEMDIR = ../../../../bin/system # Sources every command-line build (bascomp, basrun, test_suite) links: # the build pipeline, the full compiler and the runtime without forms. -COMP_SRCS = compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/obfuscate.c compiler/compact.c +COMP_SRCS = compiler/lexer.c compiler/ast.c compiler/scope.c compiler/parse.c compiler/parseExpr.c compiler/parseStmt.c compiler/parseDecl.c compiler/resolve.c compiler/resolveExpr.c compiler/typecheck.c compiler/fold.c compiler/gen.c compiler/genExpr.c compiler/genStmt.c compiler/compiler.c compiler/codegen.c compiler/strip.c compiler/obfuscate.c compiler/compact.c VM_SRCS = runtime/vm.c runtime/values.c runtime/dates.c runtime/serialize.c runtime/gfx.c runtime/screen.c # Standalone programs that link the VM also need what libdvx provides it # at run time on the target (INI access through dvxPrefs). diff --git a/src/apps/kpunch/dvxbasic/basBuild.c b/src/apps/kpunch/dvxbasic/basBuild.c index bf57cae7..3a1eb77f 100644 --- a/src/apps/kpunch/dvxbasic/basBuild.c +++ b/src/apps/kpunch/dvxbasic/basBuild.c @@ -47,6 +47,7 @@ #include "basBuild.h" #include "basRes.h" +#include "compiler/compiler.h" #include "compiler/compact.h" #include "compiler/obfuscate.h" #include "compiler/strip.h" @@ -77,8 +78,9 @@ // Growth unit of the project source buffer. #define BAS_BUILD_SOURCE_CHUNK 8192 -// Room for a BEGINFORM "name" / BEGINCLASS "name" line. -#define BAS_BUILD_WRAP_SLACK (BAS_MAX_IDENT + 32) +// Room a wrapper line needs besides its directive, name and control +// list: the space, the quotes, the newline and the NUL. +#define BAS_BUILD_WRAP_SLACK 8 // Longest [Modules]/[Forms] key, "File" plus a decimal index. #define BAS_BUILD_KEY_LEN 32 @@ -94,21 +96,23 @@ typedef enum { BAS_BUILD_PASS_COUNT } BasBuildPassE; -// The directives around a form's and a class module's code. +// The directives around a module's, a form's and a class module's code. static const char *const sWrapBegin[] = { - [BAS_BUILD_WRAP_FORM] = "BEGINFORM", - [BAS_BUILD_WRAP_CLASS] = "BEGINCLASS" + [BAS_BUILD_WRAP_MODULE] = BAS_WRAP_BEGINMODULE, + [BAS_BUILD_WRAP_FORM] = BAS_WRAP_BEGINFORM, + [BAS_BUILD_WRAP_CLASS] = BAS_WRAP_BEGINCLASS }; static const char *const sWrapEnd[] = { - [BAS_BUILD_WRAP_FORM] = "ENDFORM\n", - [BAS_BUILD_WRAP_CLASS] = "ENDCLASS\n" + [BAS_BUILD_WRAP_MODULE] = BAS_WRAP_ENDMODULE "\n", + [BAS_BUILD_WRAP_FORM] = BAS_WRAP_ENDFORM "\n", + [BAS_BUILD_WRAP_CLASS] = BAS_WRAP_ENDCLASS "\n" }; // Function prototypes (alphabetical) static bool appendSource(BasProjectSourceT *ps, const char *text, int32_t len); static int32_t appendText(const char *path, const char *name, const char *value); const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec); -bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, int32_t *textLine, int32_t *textLines); +bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, const char *ctrlList, int32_t *textLine, int32_t *textLines); bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProjectSourceT *out); void basBuildDirOf(const char *path, char *out, int32_t outSize); void basBuildFreeProject(BasProjectSourceT *out); @@ -240,17 +244,24 @@ const char *basBuildApp(const char *outPath, const BasBuildSpecT *spec) { } -bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, int32_t *textLine, int32_t *textLines) { +bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, const char *ctrlList, int32_t *textLine, int32_t *textLines) { if (wrap != BAS_BUILD_WRAP_NONE) { - char line[BAS_BUILD_WRAP_SLACK]; - int32_t n = snprintf(line, sizeof(line), "%s \"%s\"\n", sWrapBegin[wrap], name); + // One line: the directive, the name and a form's controls + const char *ctrls = (ctrlList != NULL) ? ctrlList : ""; + size_t cap = strlen(sWrapBegin[wrap]) + strlen(name) + strlen(ctrls) + BAS_BUILD_WRAP_SLACK; + char *line = (char *)malloc(cap); - if (n < 0 || n >= (int32_t)sizeof(line)) { - snprintf(ps->error, sizeof(ps->error), "Name too long: %s", name); + if (!line) { + snprintf(ps->error, sizeof(ps->error), "Out of memory."); return false; } - if (!appendSource(ps, line, n)) { + int32_t n = snprintf(line, cap, "%s \"%s\"%s\n", sWrapBegin[wrap], name, ctrls); + bool ok = appendSource(ps, line, n); + + free(line); + + if (!ok) { return false; } } @@ -315,7 +326,7 @@ bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProje const char *code = src; char formName[BAS_MAX_IDENT] = ""; - BasBuildWrapE wrap = BAS_BUILD_WRAP_NONE; + BasBuildWrapE wrap = BAS_BUILD_WRAP_MODULE; entry.firstFileLine = 1; @@ -351,7 +362,22 @@ bool basBuildConcatProject(PrefsHandleT *prefs, const char *projectDir, BasProje wrap = BAS_BUILD_WRAP_FORM; } - if (!basBuildAppendFile(out, code, (int32_t)strlen(code), wrap, formName, &entry.startLine, &entry.lineCount)) { + // A form's BEGINFORM line lists its controls (never longer than + // the .frm text that declares them) + char *ctrlList = isForm ? (char *)malloc((size_t)srcLen + 1) : NULL; + + if (isForm && (!ctrlList || basFrmControlList(src, srcLen, ctrlList, srcLen + 1) < 0)) { + snprintf(out->error, sizeof(out->error), "Out of memory."); + free(ctrlList); + free(src); + return false; + } + + bool appended = basBuildAppendFile(out, code, (int32_t)strlen(code), wrap, formName, ctrlList, &entry.startLine, &entry.lineCount); + + free(ctrlList); + + if (!appended) { free(src); return false; } diff --git a/src/apps/kpunch/dvxbasic/basBuild.h b/src/apps/kpunch/dvxbasic/basBuild.h index df19d95d..46a9b173 100644 --- a/src/apps/kpunch/dvxbasic/basBuild.h +++ b/src/apps/kpunch/dvxbasic/basBuild.h @@ -99,7 +99,7 @@ typedef struct { } BasSourceMapT; // A project's BASIC source as the compiler consumes it: every [Modules] -// file in order, then the code of every [Classes] file wrapped in +// file in order wrapped in BEGINMODULE "name" / ENDMODULE, then the code of every [Classes] file wrapped in // BEGINCLASS "name" / ENDCLASS, then the code section of every [Forms] // file wrapped in BEGINFORM "name" / ENDFORM, each file ending on a // newline. frmPaths @@ -120,18 +120,21 @@ typedef struct { // What basBuildAppendFile wraps a file's text in. typedef enum { - BAS_BUILD_WRAP_NONE, // a module: the text as it is + BAS_BUILD_WRAP_NONE, // the text as it is (a lone program) + BAS_BUILD_WRAP_MODULE, // a project's module: BEGINMODULE "name" / ENDMODULE (its Private CONST / ENUM stay in it) BAS_BUILD_WRAP_FORM, // a form's code: BEGINFORM "name" / ENDFORM BAS_BUILD_WRAP_CLASS // a class module's code: BEGINCLASS "name" / ENDCLASS } BasBuildWrapE; // Appends one file's text to *ps (a zeroed struct is a valid empty // project) and makes sure it ends on a newline, wrapped as wrap says in -// the directives naming name. textLine and textLines (either may be +// the directives naming name; a form's BEGINFORM line also lists its +// controls, ctrlList (basFrmControlList's text; NULL or "" for none). +// textLine and textLines (either may be // NULL) receive the 1-based line the text starts on and the number of // lines it occupies, wrapper lines excluded -- what an IDE source map // needs. Returns false with ps->error set on failure. -bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, int32_t *textLine, int32_t *textLines); +bool basBuildAppendFile(BasProjectSourceT *ps, const char *text, int32_t len, BasBuildWrapE wrap, const char *name, const char *ctrlList, int32_t *textLine, int32_t *textLines); // Reads the source files named by an open .dbp (paths relative to // projectDir) into *out. Returns false with out->error set on failure; diff --git a/src/apps/kpunch/dvxbasic/basRes.h b/src/apps/kpunch/dvxbasic/basRes.h index 1955cea8..1a18ef65 100644 --- a/src/apps/kpunch/dvxbasic/basRes.h +++ b/src/apps/kpunch/dvxbasic/basRes.h @@ -136,11 +136,19 @@ const char *dvxSkipWs(const char *s); // .frm scanners (shared by bascomp, basstub, basBuild and the obfuscator) // ------------------------------------------------------------ +// basFrmControlList entries: ' "Name"' -- a space and a quote before the +// name, a quote after it. +#define BAS_FRM_LIST_ENTRY_LEAD 2 +#define BAS_FRM_LIST_ENTRY_EXTRA 3 + static inline int32_t basClassCodePos(const char *text, int32_t len); static inline bool basClassHeaderLine(const char *s, int32_t len); +static inline bool basClassNameAttr(const char *text, int32_t len, int32_t *outStart, int32_t *outLen); static inline bool basExtractClassName(const char *text, int32_t len, const char *path, char *nameBuf, int32_t nameBufSize); static inline bool basExtractFormName(const char *frmText, char *nameBuf, int32_t nameBufSize); static inline int32_t basFindFormEndPos(const char *text, int32_t len); +static inline int32_t basFrmControlList(const char *frmText, int32_t frmLen, char *out, int32_t outSize); +static inline bool basFrmListHas(const char *list, int32_t listLen, const char *name, int32_t nameLen); static inline bool basLineIsWord(const char *s, int32_t len, const char *word); static inline int32_t basMatchBeginForm(const char *s, int32_t len); static inline int32_t basMatchKeywordWs(const char *s, int32_t len, const char *kw, int32_t kwLen); @@ -202,33 +210,53 @@ static inline bool basClassHeaderLine(const char *s, int32_t len) { } -// The class name of a .cls text: its Attribute VB_Name = "name" when the -// header has one, else the base name of path (without directory and -// extension). Returns false when neither gives a name. -static inline bool basExtractClassName(const char *text, int32_t len, const char *path, char *nameBuf, int32_t nameBufSize) { +// Where the name in a .cls header's Attribute VB_Name = "name" is: its +// offset and length in text (the length may be 0). False when the header +// has no such attribute. +static inline bool basClassNameAttr(const char *text, int32_t len, int32_t *outStart, int32_t *outLen) { int32_t codePos = basClassCodePos(text, len); int32_t attrLen = (int32_t)sizeof(BAS_CLS_ATTR_NAME) - 1; - nameBuf[0] = '\0'; - for (int32_t pos = 0; pos < codePos; pos++) { if ((pos == 0 || text[pos - 1] == ' ' || text[pos - 1] == '\t') && codePos - pos > attrLen && strncasecmp(text + pos, BAS_CLS_ATTR_NAME, attrLen) == 0) { const char *q = memchr(text + pos, '"', codePos - pos); - if (q) { - int32_t ni = 0; - - for (q++; q < text + codePos && *q != '"' && *q != '\n' && ni < nameBufSize - 1; q++) { - nameBuf[ni++] = *q; - } - - nameBuf[ni] = '\0'; + if (!q) { + return false; } - break; + const char *end = ++q; + + while (end < text + codePos && *end != '"' && *end != '\n') { + end++; + } + + *outStart = (int32_t)(q - text); + *outLen = (int32_t)(end - q); + return true; } } + return false; +} + + +// The class name of a .cls text: its Attribute VB_Name = "name" when the +// header has one, else the base name of path (without directory and +// extension). Returns false when neither gives a name. +static inline bool basExtractClassName(const char *text, int32_t len, const char *path, char *nameBuf, int32_t nameBufSize) { + int32_t start = 0; + int32_t nameLen = 0; + + nameBuf[0] = '\0'; + + if (basClassNameAttr(text, len, &start, &nameLen)) { + int32_t n = (nameLen < nameBufSize - 1) ? nameLen : nameBufSize - 1; + + memcpy(nameBuf, text + start, (size_t)n); + nameBuf[n] = '\0'; + } + if (nameBuf[0] == '\0' && path) { const char *base = path; @@ -365,6 +393,116 @@ static inline int32_t basFindFormEndPos(const char *text, int32_t len) { } +// The controls a .frm declares, for the BEGINFORM line of the form's code +// (the compiler then knows a bare control name: Text1 = "x", col.Add +// Text1): every "Begin Type Name" line of its Begin Form block but the +// form's own, each name (a control array's included) once, written to out +// as quoted words with a space before each (' "Text1" "Label1"'). +// Returns the length written (0 for none), or -1 when out (outSize bytes) +// is too small. +static inline int32_t basFrmControlList(const char *frmText, int32_t frmLen, char *out, int32_t outSize) { + int32_t end = basFindFormEndPos(frmText, frmLen); + int32_t outLen = 0; + int32_t pos = 0; + bool first = true; + + if (outSize <= 0) { + return -1; + } + + out[0] = '\0'; + + while (pos < end) { + int32_t l = pos; + + while (pos < end && frmText[pos] != '\n' && frmText[pos] != '\r') { + pos++; + } + + int32_t lineEnd = pos; + + while (pos < end && (frmText[pos] == '\n' || frmText[pos] == '\r')) { + pos++; + } + + while (l < lineEnd && (frmText[l] == ' ' || frmText[l] == '\t')) { + l++; + } + + int32_t b = basMatchKeywordWs(frmText + l, lineEnd - l, FRM_KW_BEGIN, FRM_KW_BEGIN_LEN); + + if (b < 0) { + continue; + } + + // Skip the type; the name follows it + int32_t n = l + b; + + while (n < lineEnd && frmText[n] != ' ' && frmText[n] != '\t') { + n++; + } + + while (n < lineEnd && (frmText[n] == ' ' || frmText[n] == '\t')) { + n++; + } + + int32_t nameLen = 0; + + while (n + nameLen < lineEnd && frmText[n + nameLen] != ' ' && frmText[n + nameLen] != '\t') { + nameLen++; + } + + // The form itself, or a line without a name + if (first || nameLen == 0) { + first = false; + continue; + } + + // A control array declares its name on several lines + if (basFrmListHas(out, outLen, frmText + n, nameLen)) { + continue; + } + + if (outLen + nameLen + BAS_FRM_LIST_ENTRY_EXTRA >= outSize) { + return -1; + } + + out[outLen++] = ' '; + out[outLen++] = '"'; + memcpy(out + outLen, frmText + n, (size_t)nameLen); + outLen += nameLen; + out[outLen++] = '"'; + out[outLen] = '\0'; + } + + return outLen; +} + + +// True when list (listLen bytes, basFrmControlList's ' "Name"' entries) +// holds name (nameLen bytes), compared without case. +static inline bool basFrmListHas(const char *list, int32_t listLen, const char *name, int32_t nameLen) { + int32_t i = 0; + + while (i < listLen) { + int32_t start = i + BAS_FRM_LIST_ENTRY_LEAD; + int32_t stop = start; + + while (stop < listLen && list[stop] != '"') { + stop++; + } + + if (stop - start == nameLen && strncasecmp(list + start, name, (size_t)nameLen) == 0) { + return true; + } + + i = stop + 1; + } + + return false; +} + + // True when s[0..len) (left-trimmed) starts with word followed by the end // of the line or a space or tab. static inline bool basLineIsWord(const char *s, int32_t len, const char *word) { diff --git a/src/apps/kpunch/dvxbasic/basrt.dhs b/src/apps/kpunch/dvxbasic/basrt.dhs index 8a04554c..fe2b31b5 100644 --- a/src/apps/kpunch/dvxbasic/basrt.dhs +++ b/src/apps/kpunch/dvxbasic/basrt.dhs @@ -72,16 +72,21 @@ Header: apps/dvxbasic/runtime/values.h BAS_TYPE_ARRAY 6 arrVal Reference-counted array. BAS_TYPE_UDT 7 udtVal Reference-counted user-defined type. BAS_TYPE_OBJECT 8 objVal Opaque host pointer (form, control). - BAS_TYPE_REF 9 refVal ByRef pointer to a BasValueT slot. + BAS_TYPE_REF 9 ref Reference (ByRef argument): ref.target is the variable, element or field it names; ref.owner keeps that storage alive. .endtable +A reference only ever lives in a call's parameters (a ByRef parameter, a ParamArray element). Read through it with basValDeref, which returns the value a reference names (or the value itself). refKind says what ref.owner is: BAS_REF_SLOT (none: a global, local or form variable), BAS_REF_ARRAY (the array, counted and locked so REDIM / ERASE raise error 10), BAS_REF_UDT (the TYPE instance holding the field), BAS_REF_OBJECT (the class instance holding the member variable) or BAS_REF_CELL (a BasRefCellT holding both the locked array and the TYPE instance, for a field of an array element). refType is the target's declared type (stores convert to it) and refLen the length of a STRING * n target. basValCopy and basValRelease count the owner. + .h2 BasValueT Tagged union holding any BASIC value. .code struct BasValueTag { - uint8_t type; // BAS_TYPE_* + uint8_t type; // BAS_TYPE_* + uint8_t refType : 5; // BAS_TYPE_REF: the target's declared type + uint8_t refKind : 3; // BAS_TYPE_REF: BasRefKindE + uint16_t refLen; // BAS_TYPE_REF: STRING * n length (0 = none) union { int16_t intVal; int32_t longVal; @@ -92,9 +97,15 @@ struct BasValueTag { BasArrayT *arrVal; BasUdtT *udtVal; void *objVal; - BasValueT *refVal; + BasObjectT *instVal; + struct { + void *owner; + BasValueT *target; + } ref; }; }; + +BasValueT *basValDeref(const BasValueT *v); // inline: what a reference names, else v .endcode .h2 Value Constructors @@ -475,7 +486,7 @@ bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, -------- ----------- basVmCallSub Call a SUB with no arguments. basVmCallSubWithArgs Call a SUB with arguments pushed onto the stack frame. - basVmCallSubWithArgsOut Call a SUB and read back modified argument values after it returns. + basVmCallSubWithArgsOut Call a SUB whose first outCount parameters are references to outArgs (set to the arguments first; each must hold a valid value): what the SUB stores in them is there when it returns, also after an error. .endtable All three push a call frame, execute until the SUB returns, then restore the previous execution state. Return true on normal completion, false on error or if the VM is not idle. diff --git a/src/apps/kpunch/dvxbasic/compiler/ast.c b/src/apps/kpunch/dvxbasic/compiler/ast.c new file mode 100644 index 00000000..9c59b024 --- /dev/null +++ b/src/apps/kpunch/dvxbasic/compiler/ast.c @@ -0,0 +1,156 @@ +// The MIT License (MIT) +// +// Copyright (C) 2026 Scott Duensing +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + + +// ast.c -- DVX BASIC syntax tree arena + +#include "ast.h" + +#include