Listening to: Hawkwind, Chronicle of the Black Sword.
I've been reading a lot of VMS BASIC  source code from the early 1990s  recently, and the one lesson it has taught me is that refactoring source code is vital for maintainability. Much of this source code originated in one program that was copied and edited whenever a new program was needed, so each new program generally ended up with a lot of rag-tag odds-and-ends code from the original program that was not actually needed for the functionality of the new program. Unfortunately, when one is trying to change some existing functionality one has to look at each and every program and figure out if that functionality is actually implemented and used in that program, and to do this you have to mentally trace the execution of the code! The moral? When you copy code, delete everything you don't actually use! Refactor, refactor, refactor!
And why didn't we use
%INCLUDE more? There are hundress of lines of
external function declarations that are used time and again in several
programs; we should have put them in a couple of include files.
I really wish VMS Basic allowed redimensioning array formal parameters, but I understand why it's not allowed. (Perhaps there should have been a specific descriptor for arrays that can be redimensioned?)
I was shocked to rediscover that there was no way of reliably building
the system from the source code. MMS was too expensive for us at the
time and if MMK existed we
didn't know about it. We used a bunch of ad-hoc DCL command
procecudures to compile whatever program we were working on (most of
them were all in one source file, and linked against one library of
utility routines) and these were never collected. So, you couldn't
easily tell if the current set of executables was actually built from
up-to-date source. Since I've been doing the odd bit of maintenance on
this system every six months or so I finally broke down and created
DESCRIP.MMS files for use with MMK, and I'm much easier in my mind
There were a number of things in this system that I think were well done, though, so it hasn't been all pain looking at it again.
Here's the text of my post to comp.os.vms:
The BASIC for OpenVMS Reference Manual says, in the fifth item of the Remarks section:
The executable DIM statement cannot be used to dimension virtual arrays, arrays received as formal parameters, or arrays declared in COMMON, MAP, or nonexecutable DIM statements.
The "no formal parameters" rule is inconvienent. It means that you can't pass an array to a function, redimension it, fill it with values, and then use LBOUND and UBOUND in the caller to find out its new size.
Presumably it is illegal because there is no way at compile time to know if the function will be called with a dynamic array created with the executable DIM statement or a static array created with a non-executable DIM statement.
However, the programmer can know, so it ought to be safe to redimension the array directly when the programmer knows it was created by an executable DIM statement.
Using Alpha BASIC V1.4-000 under OpenVMS V7.2 and looking at the listing of some code with some executable dimension statements compiled with /LIST/MACHINE revealed the existance of DBASIC$RT_DIM and after a little experimentation lead to a program that used DBASIC$RT_DIM directly to redimension dynamically dimensioned arrays in functions, included below.
DBASIC$RT_DIM is not documented for users (probably by design) and could theoretically lead to flying monkeys and access violations, and is probably bad style. However, are there any practical reasons why this wouldn't work safely? Are there any conditions under which this hack would fail to work?
Here is an example program:program redim option type = explicit, constant type = integer, & size = integer long, size = real double external sub redim_in_sub (string dim()) external long function my_redim (string dim(), long) declare long i, r i = 10 dim string vs(i) ! has to be a variable to make it an executable dim. r = my_redim (vs(), 30) print "ubound(vs):"; ubound(vs) for i = lbound(vs) to ubound(vs) \ vs(i) = "vs 30-" + num1$(i) \ next i gosub print_vs call redim_in_sub (vs()) print "ubound(vs):"; ubound(vs) gosub print_Vs exit program print_vs: for i = lbound(vs) to ubound(vs) \ print i; ": "; vs(i) \ next i return ! from print_vs end program ! redim function long my_redim (long s by value, long n) option type = explicit, constant type = integer, & size = integer long, size = real double declare long r external long function dbasic$rt_dim (long by value, long by value) r = dbasic$rt_dim (s, n) end function r ! my_redim sub redim_in_sub (string s()) option type = explicit, constant type = integer, & size = integer long, size = real double external sub set_strings (string dim(), string) declare long i call my_redim (s(), 40) for i = 0 to 40 \ s(i) = "redim in sub " + num1$(i) \ next i end sub ! redim_in_sub
(Interestingly, a slightly different approach was necessary using VAX BASIC V3.5 under VMS V5.5-2: using BY VALUE in function definition statements is not allowed by this version of VAX BASIC, and BAS$RT_DIM had to be used instead of DBASIC$RT_DIM, of course.)