Dumping BASIC variable names

bbc micro/electron/atom/risc os coding queries and routines
Post Reply
julie_m
Posts: 236
Joined: Wed Jul 24, 2019 9:53 pm
Location: Derby, UK
Contact:

Dumping BASIC variable names

Post by julie_m » Sat Aug 29, 2020 11:02 pm

Although I'm currently using BeebAsm for BCP development, and it's going very nicely, I do not want to abandon the idea of it being buildable on the target side. Otherwise, I am going to wind up with not so much a BBC Micro app, but a PC app that uses a special tool chain (BeebAsm and friends) and runtime (BeebEm or another BBC emulator).

The original target-side build system worked by having several BASIC files, each of which builds a chunk of the program and exports a *SPOOL file with the values of variables it has defined that need to be referred to in succeeding chunks. This works, but requires manually editing the list of variables each time a section of code is added or removed, which is not perfect. It would be much better if the BASIC wrapper were able to read the variable names straight from memory, rather than relying on DATA statements in the program.

So far I've done the easiest bit -- getting the variable names, just not decoding the actual values!

Code: Select all

   10REM..WORKABLE PROOF OF CONCEPT!..
   20REM..ACTUALLY DUMPS VARIABLE NAMES..
   30REM..STRAIGHT FROM BASIC MEMORY..
   40REM..
   50REM..YOU CAN ACTUALLY RUN THIS..
   60MODE7
   70PRINT"Press SHIFT to scroll."'CHR$14
   80REMS%=TRUE
   90REM
  100FORJ%=0TO3STEP3
  110P%=&900
  120[OPTJ%
  130\
  140\ &70,&71 => variable pointer
  150\ &72 => first letter of variable
  160\ &73 => last character of name
  170\
  180\  (so we can implement different
  190\   behaviour for $, % and others)
  200\
  210.code_start
  220.rewind_vars
  230\ "rewind" to beginning of list of
  240\ variables in memory
  250\
  260JMPreal_rewind_vars
  270\
  280.disp_name
  290\ display a variable name and move
  300\ the pointer on
  310\
  320\ on return Z=1 => end reached
  330\
  340JMPreal_disp_name
  350\
  360.get_name
  370\ read a variable name and return
  380\ it to BASIC, move pointer on
  390\
  400\ on return Z=1 => end reached
  410\
  420.real_get_name
  430LDA&72
  440LDY#2
  450STAvar_name-2,Y
  460INX
  470.get_name1
  480LDA(&70),Y
  490BEQget_name2
  500STA&73
  510STAvar_name-1,Y
  520INX
  530INY
  540BNEget_name1
  550.get_name2
  560LDA#13
  570STAvar_name-1,Y
  580BNEnext_var
  590.real_disp_name
  600LDA&72
  610JSR&FFEE
  620INX
  630LDY#2
  640.disp_name1
  650LDA(&70),Y
  660BEQeon
  670STA&73 \ last char in case $ or %
  680JSR&FFEE
  690INX
  700INY
  710BNEdisp_name1
  720.eon
  730.next_var
  740LDY#1
  750LDA(&70),Y
  760PHA
  770DEY
  780LDA(&70),Y
  790STA&70
  800PLA
  810STA&71
  820BEQreal_next_letter
  830RTS
  840.find_var_base
  850LDA&72
  860ASLA
  870TAX
  880LDA&400,X
  890STA&70
  900LDA&401,X
  910STA&71
  920RTS
  930.real_rewind_vars
  940LDA#ASC"@"
  950STA&72 \ initial letter
  960.real_next_letter
  970INC&72
  980JSRfind_var_base
  990LDA&71
 1000BNEfound_letter
 1010LDA&72
 1020CMP#ASC"z"
 1030BCCreal_next_letter
 1040LDA#0
 1050.found_letter
 1060RTS
 1070.dump_vars
 1080JSRreal_rewind_vars
 1090.dump_vars1
 1100LDA&71 \ 0 => end of variables
 1110BEQdump_vars_end
 1120.dump_vars2
 1130JSRreal_disp_name
 1140JSR&FFE7 \ new line
 1150LDA&71
 1160BNEdump_vars2 \ same letter again
 1170BEQdump_vars1 \ next letter
 1180.dump_vars_end
 1190RTS
 1200.var_name
 1210EQUS"                "
 1220EQUB13 \ ^ 16 spaces
 1230.code_end
 1240]
 1250NEXTJ%
 1260C$="SAVE VDUMP "+STR$~code_start+" "+STR$~code_end+" "+STR$~dump_vars
 1270PRINT'" *"C$
 1280IFS%OSCLIC$:S%=FALSE
 1290END
It uses entry points dump_vars (which prints a list of variable names, separated by newlines, to OSWRCH), rewind_vars (which "rewinds" its internal pointer to the beginning of the beginning of the list of variables in memory), disp_name (which displays the current variable name and moves the pointer on to the next one) and get_name (which reads the current name to a known location var_name in memory and appends a CR character, so it can be read using the $ operator in BASIC). Memory locations &70 and &71 are the variable pointer; &72 is the first letter of the current variable; and &73 holds the last character of the name. If this is a $ sign, the variable is a string; if a % sign, a 32-bit integer; otherwise a floating-point value.

Before you RUN the program, set S%=TRUE to make it *SAVE the assembled code. (In any case it will print out the *SAVE command for you to copy with the cursor, if you want.) When *RUN, the machine code program it will produce a dump of all BASIC variable names in memory. It does a beautiful job with its own variables .....

User avatar
jgharston
Posts: 4125
Joined: Thu Sep 24, 2009 12:22 pm
Location: Whitby/Sheffield
Contact:

Re: Dumping BASIC variable names

Post by jgharston » Sat Aug 29, 2020 11:54 pm

See VList source code.

Code: Select all

$ bbcbasic
PDP11 BBC BASIC IV Version 0.32
(C) Copyright J.G.Harston 1989,2005-2020
>_

julie_m
Posts: 236
Joined: Wed Jul 24, 2019 9:53 pm
Location: Derby, UK
Contact:

Re: Dumping BASIC variable names

Post by julie_m » Sun Aug 30, 2020 4:08 pm

jgharston wrote:
Sat Aug 29, 2020 11:54 pm
See VList source code.
I'm certainly not claiming any originality on this idea, and it would be disingenuous of me to pretend that the name jgharston did not crop up a few times while I was searching for information on how to do this!

However, as a fan of doing things the hard way just for fun, I tried to discover as much as possible for myself. And just like a crossword offering an insight into the darkest recesses of the setter's mind, I found it a fun and illuminating exercise. So here is my latest version:

Code: Select all

   10REM..VARIABLE DUMP..
   11REM..
   12REM..JULIE KIRSTY LOUISE MONTOYA..
   13REM..
   14REM..THIS SOFTWARE IS DEDICATED..
   15REM..TO THE PUBLIC DOMAIN 2020..
   16REM..
   17REM..USE, ABUSE, ENJOY, DESTROY..
   20REM..
   30REM..ASSUMES EVERYTHING NUMERIC IS..
   40REM..A 16-BIT UNSIGNED INTEGER..
   50REM..THIS IS FINE FOR ADDRESSES..
   60REM..
  110REM..VARIABLES BEGINNING WITH..
  120REM.._ OR ` WILL NOT BE DUMPED..
  130REM..
  140REM..L% => STARTING LINE NUMBER..
  150REM..
  160REM..IF L%=0 THEN NO LINE NUMBERS..
  170REM..WILL BE INSERTED AND THE..
  180REM..VARIABLE DEFINITIONS WILL BE..
  190REM..IMMEDIATE MODE STATEMEMTS..
  200MODE7
  210PRINT"Press SHIFT to scroll."'CHR$14
  220REMS%=TRUE
  230FORJ%=0TO3STEP3
  240P%=&900
  250[OPTJ%
  260\
  270\ &70,&71 => variable pointer
  280\ &72 => first letter of variable
  290\ &73 => last character of name
  300\
  310\  (so we can implement different
  320\   behaviour for $, % and others)
  330\
  340.code_start
  350.rewind_vars
  360\ "rewind" to beginning of list of
  370\ variables in memory
  380\
  390JMPreal_rewind_vars
  400\
  410.get_name
  420\ read a variable name and return
  430\ it to BASIC, move pointer on
  440\
  450\ on return Z=1 => end reached
  460\ X is increased for every char
  470\
  480JMPreal_get_name
  490\
  500.disp_name
  510\ display a variable name and move
  520\ the pointer on
  530\
  540\ on return Z=1 => end reached
  550\
  560JMPreal_disp_name
  570\
  580.disp_value
  590\ disp variable value, working out
  600\ data type automatically
  610\
  620JMPreal_disp_value
  630\
  640.real_get_name
  650LDA&72
  660STA&73 \ clear $/% sign
  670LDY#2
  680STAvar_name-2,Y
  690INX
  700._get_name1
  710LDA(&70),Y
  720BEQ_get_name2
  730STA&73
  740STAvar_name-1,Y
  750INX
  760INY
  770BNE_get_name1
  780._get_name2
  790LDA#13
  800STAvar_name-1,Y
  810BNEcopy_value
  820.real_disp_name
  830LDA&72
  840STA&73 \ clear $/% sign
  850JSR&FFEE
  860INX
  870LDY#2
  880._disp_name2
  890LDA(&70),Y
  900BEQcopy_value
  910STA&73 \ last char in case $ or %
  920JSR&FFEE
  930INX
  940INY
  950BNE_disp_name2
  960.copy_value
  970\
  980\ Copy the value to &74-&79
  990\
 1000\ This will definitely include ALL
 1010\ bytes from the value
 1020\
 1030TXA \ stash X
 1040PHA
 1050LDX#0
 1060INY \ skip zero marker
 1070._copy_value1
 1080LDA(&70),Y
 1090INY
 1100STA&74,X
 1110INX
 1120CPX#6
 1130BCC_copy_value1
 1140PLA \ retrieve X
 1150TAX
 1160LDY#1
 1170LDA(&70),Y
 1180PHA
 1190DEY
 1200LDA(&70),Y
 1210STA&70
 1220PLA
 1230STA&71
 1240BEQreal_next_letter
 1250RTS
 1260.real_rewind_vars
 1270LDA#64 \ ASC"@"
 1280STA&72 \ initial letter
 1290.real_next_letter
 1300INC&72
 1310LDA&72
 1320\
 1330\ Skip everything after "Z" and
 1340\ before "a". Variables starting
 1350\ with _ or ` won't be dumped.
 1360\
 1370CMP#91 \ ASC"["
 1380BCC_next_letter1
 1390CMP#97 \ ASC"a"
 1400.skip_Ztoa
 1410BCCreal_next_letter
 1420\
 1430\ NOP out the above instruction
 1440\ to include _ and ` vars
 1450\
 1460._next_letter1
 1470LDA&72
 1480ASLA
 1490TAX
 1500LDA&400,X
 1510STA&70
 1520LDA&401,X
 1530STA&71
 1540BNE_next_letter2
 1550LDA&72
 1560CMP#123 \ ASC"{"
 1570BCCreal_next_letter
 1580LDA#0
 1590._next_letter2
 1600RTS
 1610.real_disp_value
 1620\
 1630\ Work out what type of variable
 1640\ we are dealing with
 1650\
 1660LDA&73
 1670CMP#37 \ ASC"%"
 1680BEQdisp_int
 1690CMP#36 \ ASC"$"
 1700BNEdisp_float
 1710.disp_str
 1720\
 1730\ &74, &75 => address
 1740\ &76 => bytes allocated
 1750\ &77 => actual length
 1760\
 1770LDY#0
 1780LDA#34
 1790JSR&FFEE
 1800.ss1
 1810LDA(&74),Y
 1820JSR&FFEE
 1830CMP#34 \ double "speech marks"
 1840BNEss2
 1850JSR&FFEE \ write again
 1860.ss2
 1870INY
 1880CPY&77
 1890BNEss1
 1900LDA#34
 1910JMP&FFEE
 1920\
 1930.disp_float
 1940\
 1950\ This works like resistor colours
 1960\ &74 => multiplier band
 1970\ &75 => highest digit
 1980\ &76
 1990\ &77
 2000\ &78 => lowest digit
 2010\
 2020\ We are assuming it is a 16-bit
 2030\ unsigned integer!
 2040\
 2050LDA&74 \ check mutiplier <> 0
 2060BNE_disp_float1
 2070STA&75
 2080BEQdisp_int
 2090._disp_float1
 2100LDA#0
 2110STA&79
 2120STA&7A
 2130STA&7B
 2140STA&7C
 2150LDA&75
 2160ORA#&80
 2170STA&75
 2180LDA&74
 2190AND#&7F
 2200TAX
 2210BEQ_disp_float3
 2220._disp_float2
 2230ASL&78
 2240ROL&77
 2250ROL&76
 2260ROL&75
 2270ROL&79
 2280ROL&7A
 2290ROL&7B
 2300ROL&7C
 2310DEX
 2320BNE_disp_float2
 2330._disp_float3
 2340LDA&79
 2350STA&74
 2360LDA&7A
 2370STA&75
 2380.disp_int
 2390LDY#0
 2400._disp_int1
 2410LDA&74
 2420AND#&F
 2430CLC
 2440ADC#48 \ ASC"0"
 2450CMP#ASC":"
 2460BCC_disp_int2
 2470ADC#ASC"A"-ASC":"-1 \ C=1 !
 2480._disp_int2
 2490STAvar_name,Y
 2500LDX#4
 2510._disp_int3
 2520LSR&75
 2530ROR&74
 2540DEX
 2550BNE_disp_int3
 2560LDA&74
 2570ORA&75
 2580BEQ_disp_int4
 2590INY
 2600BNE_disp_int1
 2610._disp_int4
 2620LDA#38 \ ASC"&"
 2630JSR&FFEE
 2640._disp_int5
 2650\
 2660\ Display digits in reverse order
 2670\ by DECreasing Y
 2680\
 2690LDAvar_name,Y
 2700JSR&FFEE
 2710DEY
 2720BPL_disp_int5
 2730\
 2740\ Y<0 => we have displayed units
 2750\
 2760RTS
 2770\
 2780.disp_lineno
 2790\
 2800\ L% at &430 => line no
 2810\
 2820\ Copy L% to &74,&75
 2830\
 2840LDA&430
 2850STA&74
 2860LDA&431
 2870STA&75
 2880ORA&74
 2890\ Skip if L%=0
 2900BEQno_lineno
 2910\ Skip if L%=0
 2920LDA&430
 2930\
 2940\ Add 10 to L%
 2950\
 2960CLC
 2970ADC#10
 2980STA&430
 2990BCC _disp_ln1
 3000INC&431
 3010._disp_ln1
 3020\
 3030\ Divide &74,&75 by 10
 3040\ Quotient to &74,&75
 3050\ Remainder to &76,&77
 3060\
 3070LDY#0
 3080._disp_ln2
 3090TYA \ stash Y
 3100PHA
 3110LDA #0
 3120STA &76 \ zero out remainder
 3130STA &77
 3140LDY#17 \ one more than we need
 3150BNE _divide3 \ extra left shift on bottom bits
 3160._divide1
 3170ROL &76
 3180ROL &77
 3190._divide2
 3200SEC
 3210LDA &76
 3220SBC #10
 3230TAX \ stash low byte in X
 3240LDA &77
 3250SBC #0
 3260BCC _divide3
 3270\ update dividend if we had room to subtract
 3280STX &76
 3290STA &77
 3300._divide3
 3310ROL &74 \ C shifts bit of quotient into divd
 3320ROL &75
 3330DEY
 3340BNE _divide1
 3350PLA
 3360TAY
 3370LDA&76
 3380CLC
 3390ADC#48 \ "0"
 3400STAvar_name,Y
 3410\ keep going till nothing left
 3420LDA&74
 3430ORA&75
 3440\
 3450\ Display digits backwards
 3460\
 3470BEQ_disp_int5
 3480INY
 3490BNE_disp_ln2
 3500.no_lineno
 3510RTS
 3520\
 3530.dump_vars
 3540JSRreal_rewind_vars
 3550._dump_vars1
 3560LDA&71 \ 0 => end of variables
 3570BEQdump_vars_end
 3580._dump_vars2
 3590JSRdisp_lineno
 3600JSRreal_disp_name
 3610LDA#61 \ ASC"="
 3620JSR&FFEE
 3630JSRreal_disp_value
 3640JSR&FFE7 \ new line
 3650LDA&71
 3660BNE_dump_vars2 \ same letter again
 3670BEQ_dump_vars1 \ next letter
 3680.dump_vars_end
 3690RTS
 3700\
 3710.var_name
 3720EQUS"                "
 3730EQUB13
 3740.code_end
 3750]
 3760NEXTJ%
 3770C$="SAVE VDUMP "+STR$~code_start+" "+STR$~code_end+" "+STR$~dump_vars
 3780PRINT'" *"C$
 3790IFS%OSCLIC$:S%=FALSE
 3800END
This generates a dump of almost all BASIC variables -- excluding the permanent integer variables @% to Z%, and any variables whose names begin with an underscore or a pound sign, so you can prevent some variable names from being exported; see examples in the code above -- in the form of assignment statements that BASIC will understand, if you have *SPOOL active while the dump is running, you can read them back with *EXEC. String values are properly surrounded by "speech marks", and embedded speech marks within a string are printed twice. Numeric values are rendered in hex, preceded by an "and" sign.

The permanent variable L% controls program behaviour. If L% is zero, then the variables will be dumped as a series of immediate-mode commands. But if L% is greater than zero, it will be treated as a starting line number for the variable assignment statements. The step size of 10 is hard-coded into the program; good job it's Open Source :)

You will also notice I did some very serious cheating with the display floating-point numbers! I am not sorry for this. There is doing things the hard way just for fun, and there is deliberate self-destructive behaviour! The intended purpose of this code is to export the labels defined while assembling a section of code, so that another section of code can be assembled against them. As such, it is quite reasonable to suppose that every numeric variable to be dumped will be either a single byte, or an address in the 6502's addressable space; that is to say, a 16-bit unsigned integer.

Of course there are also entry points provided so you can use this code in conjunction with BASIC to deal with awkward values.

Disc image:
var_dump.ssd
(10 KiB) Downloaded 4 times

Post Reply

Return to “programming”