6

Question

Was there any BASIC or extension or toolbox for BASIC allowing to PEEK multiple bytes from memory in one instruction - besides the often-used DPEEK (or alike) to read a word?

I'm asking specifically for a PEEK-like general purpose function, not specialized ones like for screen access or similar and especially not any kind of programming tricks.

Giving examples of specialized functions in addition will still be nice for comparison and bonus points.


Background

I just learned of an awesome new piece of ancient, ancient usage from an answer given by Tofro. There has been a BASIC extension for the Sinclair Spectrum allowing to POKE whole byte sequences to memory. In Beta BASIC a line like

40 POKE 16384,A$

will poke the content of A$ into address 4000h and the following memory.

This is such a genuine simple and perfect sensible implementation. By now I'm mad at myself. Over the years I created many solutions beating around that bush, but that I've never thought about utilizing PEEK that way. It's brilliant.

Sure, it introduces a little bit of polymorphism into BASIC, which otherwise only goes for a fixed type and optional conversion, like float to int with conventional PEEK/POKE, but that has been done in other places as well.

Now while extending POKE is straight forward (from a language point), the much needed counterpart of PEEK is not. That's mainly due the fact that BASIC strings are by default of variable length, thus the interpreter can not take the length to be read from the string (*1,2). Tofro cites in his answer a special access function:

20 LET A$=MEMORY$()(16384 TO 22527)

Not that I would call this elegant, but more important, it's a quite Sinclair-ish notation. So my desire would be to find something more generic with a syntax compatible to more .. lets say standard BASIC.

And that's what I'm looking for.


P.S.:

A discovery like this tickles my urge to code. I want this. So far my own approach would be an extension to PEEK like

<var> = PEEK(<adr> [,<len])

Here compatibility is maintained by having an implied length of 1 if not given. In addition it also needs to act differently based on the type of the receiving variable.

  • If it's an integer (A%) then the bytes addressed get assigned as integer, so optional twisted for byte order (replacing the DPEEK as well).

  • If it's a string (A$) then they get just assigned there.

  • If it's a float (A, A!) everything gets blurry and must be settled by definition.

In fact, having such a function adds several possible errors - like selecting a range to large for the string data type (many BASICs can do only 255 char), or for an integer selecting more bytes than fit in one, and so on.

So I'm even more interested in what kind of solutions in that area were available back then (or today) in BASIC.


*1 - Ignoring for the moment, that this again would be a violation of the basic BASIC design.

*2 - Yes, it would be possible to make the string to contain the wanted amount of bytes beforehand (like in A$=SPACE$(16)) but that's not only clumsy but also a horrible design, quite hard to read and error prone.

  • when designing something yourself, reading into an array comes to mind as well. or maybe mapping as an array, to avoid copying... – Felix Palmen Aug 28 at 10:58
  • @FelixPalmen Hmm, I usually would envision an array as some kind of multiple instance of single variables. So assigning memory data to a string arra seams overly complex. Mapping is a complete different issue. Great idea, and some BASIC like QL?) do allow similar tricks, but thats past the PEEK idea here, This is really about something as basic as PEEK for memory access. – Raffzahn Aug 28 at 11:25
  • Just an array of integers of course, similar to a byte[] in some more recent languages. Could allow direct access to a table with less overhead (indexing). – Felix Palmen Aug 28 at 11:30
  • @FelixPalmen Jup, that's mustly like the BetterBasic notation of indexing. Here in place operation (aliasing) instead of assigning of values is a great plus. Having such will be useful. Still that's not what I'm looking for. this is really about a factual assignment like PEEK does – Raffzahn Aug 28 at 11:37
  • 1
    In my opinion, the MEM device is even more flexible (provided your BASIC/OS can handle random access files well). Beyond strings, it allows to PEEK and POKE other non-scalar types like (multi-dimensional) arrays and other structures in case your BASIC knows such things. – tofro Aug 28 at 11:42
14

BBC BASIC didn't use the PEEK or POKE keywords, but had the ? operator and statement which had the same effect. So the statement ?128 = 0 is equivalent to POKE 128, 0, and the expression ?128 is equivalent to PEEK 128. However, it also had ! and $ which did 32-bit and string peeks and pokes and e.g. $128 = "HELLO" would write the ASCII bytes of "HELLO" into locations 128–132, and terminate the string with a carriage-return (0x0D) terminator in location 133.

Syntax such as A?3 could also be used and was equivalent to ?(A+3). This is obviously useful for packing and unpacking structures. This also leads to confusing syntax such as A$1 = "foo". [Deleted as this turns out to not be valid syntax.] (BBC BASIC written with all of the efficiency tricks turned up to eleven could be hard to read.)

The terminator in the string operation was occasionally undesired, since it might either be unnecessary and clobbering something else in memory, or was the wrong terminator and required a bit of a dance to work around. Such strange limitations quickly push people towards the assembler...

  • 1
    I'm a bit confused as how that works with the basic use of '?' in BASIC as abrevation of print. ?128 should print out a line spelled "128<cr>", souldn't it? – Raffzahn Aug 28 at 11:21
  • 1
    @Raffzahn Apparently it's known as the query operator. That said, I don't remember if it existed or not on the actual machines. My fuzzy memories of using BBC Basic years ago and wanting direct memory access involved using inline assembly. Or rather, copying inline assembly out of magazines, because at that young age I knew enough to do Basic but not assembly. – Dranon Aug 28 at 13:57
  • 5
    BBC Basic used P. for the print abbreviation. IIRC, most of BBC Basic shortcuts were of the letter + . variety – SeanC Aug 28 at 19:35
  • 1
    According to the BBC Basic VI manual (page 163), the operators ! and ? can be used in two ways - A?5 or ?(A+5) - while the operators $ and | only support the variant $(A+8) but not A$8. – Martin Rosenau Aug 29 at 5:49
  • BBC Basic V also introduced the | operator to peek and poke 40-bit floats. – Soruk Aug 29 at 10:43
8

ZX Spectrum Beta Basic

MEMORY$ is a pseudo-variable that considers the whole 64k of memory a string. By slicing it (Sinclair's way of implementing LEFT$, RIGHT$,...) you can pick memory areas into string variables.

10 REM Move memory to a string
20 LET a$=MEMORY$()(16384 TO 22527)
30 REM Move back
40 POKE 16384,a$

Sinclair QL SuperBASIC and Turbo Toolkit Basic extension

Standard QL BASIC nows how to PEEK and POKE scalar integer types to/from memory

10 x$ = PEEK (x)     : REMark PEEK a byte
20 POKE x, x$        : REMark POKE it back
30 x% = PEEK_W (x)   : REMark PEEK a 16-bit word
40 POKE_W (x, x%)    : REMark POKE it back
45 REMark Note Sinclair QL BASIC doesn't have a 32-bit int type
50 x = PEEK_L (y)    : REMark PEEK a 32-bit long, convert it to float
60 POKE_L (y, x)     : REMark convert float to long, poke it
70                   : REMark last two obviously only work on even addresses

Some toolkits (BASIC extensions) did the logical thing and implemented the same for strings (just like you proposed):

10 x$ = PEEK$ (x)       : REMark assume x holds a string in standard QL format
20                      : REMark (word length, then characters), fill x$ with it
30 POKE$ (x, x$)        : REMark POKE it back
40 x$ = PEEK$ (x, len)  : REMark Does the same thing, but takes the string length
50                      : REMark as an argument, and PEEKs only the characters

You also had the same for floating point variables

10 x = PEEK_F (y)    : REMark assumes y holds 6-byte FP in QL native format
20 POKE_F (y, x)     : REMark poke it back without first converting to byte

Sinclair QL MEM pseudo-device

Another elegant (in my opinion, even more elegant than the above) approach to do the same thing is to supply a pseudo-device driver that pretends the memory is a random-access device:

10 OPEN #4, MEM
20 GET #4\x,x$       : REMark position file pointer (i.e. address) to x, then read string x$
30 PUT #4\x,x$       : REMark put it back
40 CLOSE #4          : REMark close the "file"

Note this works with any other type as well, including arrays (and is, thus, way more flexible than the above approach of extending PEEK and POKE). The MEM device driver I am working with has the added feature that you can add an address to the device name like

10 a = ALCHP (1024)   : REMark allocate 1k of common heap, put the address into a
20 OPEN #4,"MEM_" & a : REMark device name is now MEM_<address>. 
30                    : REMark You can now work with relative offsets from a
40                    : REMark and not from 0

Because the QL had some rudimentary network and a file server function, you could even open the "MEM" pseudo device on a server, and "PEEK and POKE" there:

10 OPEN #4,NET1_MEM_131072

Would open #4 to the screen memory of the QL with network station address 1. You could then save (or modify) the screen from the other box on a file on your local drives. Not extactly "safe", but nerdy ;)

  • Oops? why deleting it? Anyway, Thanks a lot. great writeup. So sinclair used different PEEKs. Ok. The Memory-Device is cool. But when using with a base address, tehre is no upper likint, right? Allocating 1K on the heap would still allow it to write past that length? – Raffzahn Aug 28 at 11:19
  • 1
    Somehow created a duplicate answer. Yes to your question: No airbags, no belts, no braces in that specific driver. You could, however, extend the device name encoding to implement an upper limit, like "MEM_<lower>_<upper>" and range check access - The QL's device name encoding allowed that. But that was not part of that specific implementation. – tofro Aug 28 at 11:19
2

Although it didn't let you assign data to a variable directly, GW-BASIC had BSAVE, which in conjunction with DEF SEG, allowed you to store from anywhere in memory to a file:

10 REM Save some BIOS space
20 DEF SEG=&HF800
30 BSAVE "BIOS.BIN", 0, 255

BLOAD worked as the reverse of this, with the interesting feature that the loaded file contained the original offset and length, and you could relocate the loaded data (but not alter its length):

10 REM Restore the BIOS space to a string
20 REM DEF SEG isn't needed as it defaults to GW-BASIC's DS
30 DIM S$=STRING$(0,255)
30 BLOAD "BIOS.BIN", VARPTR(S$)

NOTE: This should work but I haven't tested it; it's been a long time since I've done this.

2

AMOS Basic had, in addition to the usual 8-bit Peek()/Poke, double-length Deek()/Doke and the 32-bit (longword) Leek()/Loke. This was probably inherited from STOS.

Likewise, Blitz Basic had Peek.b()/Poke.b, where the .b could be replaced by .w or .l, reflecting 68000 assembly language usage.

1

Oric-1 and Oric Atmos

had the DEEK(address) and the statement DOKE address, word alongside of PEEK and POKE that worked on 16 bit values. It was really handy as it replaced the very common

  POKE A, V AND 255 : POKE a+1, v / 256
  DOKE A, V

or

  LET V=PEEK(A)+256*PEEK(A+1)
  LET V=DEEK(A)
1

The GFA BASIC has several PEEK and POKE instructions:

  • PEEK(addr): reads one byte at addr
  • DPEEK(addr): reads a word (2 bytes) at addr
  • LPEEK(addr): reads a long word (4 bytes) at addr

along with their counterparts:

  • POKE addr, byte
  • DPOKE addr, word
  • LPOKE addr, long

(and also SPOKE, SDPOKE and SLPOKE to put the 68000 into supervisor mode before writing, at least on the Atari ST and Amiga versions)

In addition to that, it also has the BMOVE instruction to copy any number of bytes from a memory area to another (even if they are overlapping):

BMOVE source_addr, target_addr, n
0

POKE and PEEK were used a lot in the Commodore 64 BASIC V2, as it was completely lacking in high-level commands to manipulate the screen, sprites, IO and so on.

For example:

POKE 53281, 0

would set the screen background to black.

Conversely

C = PEEK(53281)

would return an integer corresponding to the screen background colour and place it in variable 'C'.

  • 2
    How is this related to the question? – Felix Palmen Aug 28 at 13:38
  • 1
    I probably misunderstood the question title now that I read it again. – Alan B Aug 28 at 13:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.