Tuesday, November 11, 2025
Extending the syntax when calling assembly language subroutines for Color BASIC
A few days ago on the Color Computer mailing list, Allen Huffman asked:
The BASIC ROM has the
USRfunction:
DEF USR0=&H3F00
A=USR0(42)It accepts one parameter.
Since it jumps from BASIC into the
USRassembly, couldn’t that code just parse a “,” and more numbers, allowing it to accept whatever needed to be passed in?
A=USR0(1,2,3,4)
[Coco] USRx() and adding more parameters?
Since we're talking about a 45 year old computer and with zero chance of a newer version of BASIC coming out any time sooner, the answer is “yes,” if you don't mind digging through the Unravelled Series (a series of books giving a disassembly of the Color Computer BASIC ROMs) and calling a bunch of nearly undocumented routines.
We'll start with the following code that implements a 16-bit version of PEEK,
using some include files I wrote:
include "basic.i" .opt basic defusr0 peekw org $7F00 peekw jsr INTCVT ; convert parameter to integer tfr x,d ; transfer into an index register ldd ,x ; read 16 bits from memory jmp GIVABF ; return value to BASIC end
Now,
a 16-bit version of PEEK is nice,
but it would also be nice to have a 16-bit version of POKE.
But USRn only takes one parameter;
if we want an aditional value,
we're going to have to extend the BASIC parser.
Fortunately,
due to the way Color BASIC works,
this is easy and there are quite a few routines we can call.
The first call is to a routine I'm calling CB.evalcomma
(as in the Unravelled Series it goes by the name of LB26D).
We need the comma,
otherwise BASIC will return a syntax error without it.
The next call we need is to a routine I'm calling CB.eval,
which just evaluates an expression.
And that's pretty much all that is needed to parse the additional syntax needed for this.
The code looks like:
include "basic.i" include "basic-internal.i" .opt basic defusr1 pokew org $7F00 pokew jsr INTCVT ; convert parameter to integer pshs d ; save the address jsr CB.evalcomma ; parse past comma jsr CB.eval ; parse expression jsr INTCVT ; convert expression to integer puls x ; get address std ,x ; write data into address rts ; return to BASIC
And to use this:
DUMMY=USR1(&H76),1234
As it was pointed out,
the code that handles USRn parses an expression in parentheses,
so a modified USRn that parses would have to look something like A=USR0(1),2,3,4.
It's unfortunate that we can't just call USR1() without using the result,
but that's a limitation of ColorBASIC which requires the results of USRn to be used.
Other than that,
we have successfully extended BASIC to modify how USRn works.
This can also work with EXEC,
the other way assembly code is called in BASIC
(I'll leave that as an exercise for the reader).
But there are issues:
DUMMY=USR0(&H76),&HA000 ?FC ERROR OK
The issue—INTCVT checks if the value being converted is between -32,768 and 32,767
(a signed 16-bit value).
The value of &HA000 is 40,960
(which still fits in 16-bits, but is unsigned)
and thus,
we get the ?FC ERROR from BASIC.
This issue also affects the PEEKW function as we can't easily peek ROM addresses.
If we wanted to peek the 16-bits at address 40,960,
we would need to pass in the value of -24,576.
That will work,
but the value returned would be -24,117,
which is $A1CB,
the address that is stored at address 40,960
(or $A000 in hexadecimal).
It would be nice if we could correct these issues.
In looking through the Unravelled Series,
I did find two routines that help.
The first is CB.uintcvt.
Like INTCVT this returns a 16-bit value in the D register,
but doesn't signal an error if the value is outside -32,768 and 32,767.
The other routine is CB.addrcvt,
which returns the 16-bit value in the X register.
We can thus rewrite our POKEW function as:
include "basic.i" include "basic-internal.i" .opt basic defusr1 pokew org $7F00 pokew jsr CB.addrcvt ; convert parameter to an address pshs x ; save the address jsr CB.evalcomma ; parse past comma jsr CB.eval ; parse expression jsr CB.uintcvt ; convert expression to integer puls x ; get address std ,x ; write data into address rts ; return to BASIC
And it all works as expected.
But that still leaves the PEEKW function returning negative values.
Again,
it would be nice if we could return an unsigned result to BASIC.
I scanned the Unravelled Series but I could not find a routine to call.
Perhaps I didn't look hard enough,
but I did come up with a workaround.
It's not pretty,
but it works.
include "basic.i" .opt basic defusr0 peekw org $7F00 peekw jsr INTCVT ; convert parameter to integer tfr x,d ; transfer into an index register ldd ,x ; read 16 bits from memory bmi .neg jmp GIVABF ; return value to BASIC .neg std FP0 ; return negative value as unsigned lda #$90 ; to BASIC sta FP0EXP clr FP0+2 clr FP0+3 clr FP0+4 rts end
For positive values,
I still use GIVABF.
For negative values,
I construct the floating point value “by hand” since all integer values from 32,768 to 65535 use the same floating point exponent.
All I can say is “it works.”
And given how seldom I've wanted to return an unsigned value,
I can't say that it's a bad solution for this one case.
Then I got to thinking—could I combine the two routines into one?
One function that can either peek or poke a 16-bit value?
Where X=USR0(&H76) would read a 16-bit value,
and X=USR0(&H76),&HA000 would write a 16-bit value while returning the original 16-bit value?
I mean,
why not make the return value for poking memory do something?
But this means peeking ahead for a comma and deciding what to do.
Fortunately,
there are enough examples of this in ColorBASIC that's it's relatively straight forward—the next character in the input can be found by the pointer
stored at address CB.charad,
and wouldn't you know it,
the MC6809 can read through a pointer at an address to get it:
ldb [CB.charad] ; CB.charad contains an address cmpb #',' ; is it a comma?
So now we can combine the two routines:
include "dp.i" include "basic.i" include "basic-internal.i" .opt basic defusr0 peekpokew org $7FD0 peekpokew jsr CB.addrcvt ; return parameter in X ldd ,x ; read 16-bit at address pshs x,d ; save address and data ldb #',' ; check for comma cmpb [CB.charad] bne .return ; if none, just a peek jsr CB.evalcomma ; else parse the comma jsr CB.eval ; evaluate the expression jsr CB.uintcvt ; convert to 16-bit unsigned std [2,s] ; store expr in address .return puls x,d ; restore data (and stack) tsta ; test data < 0 bmi .neg ; if so, handle jmp GIVABF ; return pos via BASIC .neg std FP0 ; return negative as unsigned lda #$90 sta FP0EXP clr FP0+2 clr FP0+3 clr FP0+4 rts .pcle $8000 end
The PCLE directive stands for “PC register Less Than or Equal to” and is there to ensure our code doesn't run into ROM.
If it does, it generates an error:
pw.asm:30: error: E0106: PC 8030 exceeds given limit 8000
and one can adjust the origin appropriately.
There are also some other functions I found,
CB.evalopar which parses an open parenthesis,
and CB.evalcpar which parses a close parenthesis.
Then there's ECB.evalpoint which parses two expressions in parentheses:
(x,y) and places the results in the variables ECB.horbeg
(horizontal beginning)
and the second into ECB.verbeg
(verical beginning)—nice for parsing X,Y coordinates for graphics,
and ECB.evalrect which parses a pair of points:
(x1,y1)-(x2,y2) and places the second set of values into ECB.horend and ECB.verend.
This can lead to some weird looking BASIC code:
X=USR3(4),(X1,Y1)-(X2,Y2)
but it works.
Monday, November 03, 2025
Limitations of a two-pass assembler
I've come to realize that supporting foward references in a two-pass assembler isn't always easy. The simple case of forward references I support:
lda #alpha alpha equ 5
On pass 1, alpha isn't defined, but by pass two, we have its value—5.
With this code, however:
lda #alpha alpha equ bravo bravo equ 5
alpha is undefined on line 1, and it remains undefined even on line 2 because we haven't defined bravo yet. Thus when we end pass 1, alpha is still undefined. That it took me two years to even stumble across this issue is a bit surprising to me. I just haven't written 6809 assembly code like this.
Can I fix this? If I add another pass, probably. If I don't want to add another pass … I don't know. I would have to track expressions that aren't fully defined in pass 1, which could be a lot of work for an issue that might not come up all that often (if my own code is to go by). I mean, things can get quite pathological:
lda #Alpha Alpha equ Bravo+1 Bravo equ Charlie+1 Charlie equ Delta+1 Delta equ Echo+1 Echo equ Foxtrot+1 Foxtrot equ Golf+1 Golf equ Hotel+1 Hotel equ India+1 India equ Juliet+1 Juliet equ Kilo+1 Kilo equ Lima+1 Lima equ Mike+1 Mike equ November+1 November equ Oscar+1 Oscar equ Papa+1 Papa equ Quebec+1 Quebec equ Romeo+1 Romeo equ Sierra+1 Sierra equ Tango+1 Tango equ Uniform+1 Uniform equ Victor+1 Victor equ Whiskey+1 Whiskey equ Xray+1 Xray equ Yankee+1 Yankee equ Zulu+1 Zulu equ 1
lsawm
(part of LWTools)
does properly handle this pathological case but it does six passes,
not two.
The other 6809 assembler I have,
an older one written back in the 90s,
doesn't and issues deceptive error messages,
so it's not like I'm the only one to not handle this properly.
As of now, I just issue an error and let the programmer deal with it.
Friday, October 31, 2025
For Hallowe'en, I'm half hoping we get all the kids so we have no candy left, and half hoping we get no kids so we have all the candy left
For the past few years, fewer and fewer kids have been showing up on our door step looking for candy. That doesn't stop Bunny from being opimistic and overbuying candy just in case scores of kids come by and get candy, least we get our house redecorated with eggs and toilet paper. Me, I've always been surprised when any kids show up these days on Hallowe'en. So it was that Bunny bought way more candy than I felt we needed.
We ended up with three kids showing up. In one group. At around 7:30 pm. At least we avoided carving a pumpkin this year, opting instead for my ceramic, pre-carved pumpkin.
The good news?
Bunny bought candy we like.
The bad news?
Bunny bought candy we like.
It won't go to waste, but it will go to our waist.
Tuesday, October 21, 2025
The actual root cause of yesterday's bug were laid over twenty years ago
Yesterday,
I found the root cause of a bug but I did not go into details about how that bug slipped into production
(so to speak).
That's easy—the configuration of mod_blog differ between my development server and public server.
On my public server, I have the following bit of code in the configuration:
process = require("org.conman.process")
-- --------------------------------------------------------------------
-- process limits added because an earlier version of the code actually
-- crashed the server it was running on, due to resource exhaustion.
-- --------------------------------------------------------------------
process.limits.hard.cpu = "10m" -- 10 minutes
process.limits.hard.core = 0 -- no core file
process.limits.hard.data = "20m" -- 20 MB
-- --------------------------------------------------------
-- We now resume our regularly scheduled config file
-- --------------------------------------------------------
I load a module to configure bits of the environment that mod_blog runs in.
The configuration file on the development server does not have such code.
So when I compiled the email notification program,
the fact that I did not include the -rdynamic compiler option was not an issue when I ran my tests.
Yes,
a case where there was a difference between development and production that allowed a bug to slip through.
So I decided to dig a bit deeper.
A few days ago I explained why I had such directives in my configuration file when
I was asked why didn't I use Apache's RLimitMEM directive.
I answered that the cause of adding the process limits happened pretty early in the use of mod_blog and that I didn't recall seeing such a directive in Apache at the time.
But I did get curious as to when Apache might have added the RLimitMEM directive.
I started this site using Apache 1.3
(when that was the current version of Apache—I've been blogging for quite a long time)
and I was thinking that the RLimitMEM directive may have been added around version 2.0.
In my archives,
I found a copy of Apache 1.3.9 and wouldn't you know it—RLimitMEM existed!
Sigh.
I could have avoided yesterday's issue had I only read a bit further into the Apache documentation back in the day.
Monday, October 20, 2025
It worked, but it failed
In posting the previous post I encounted an interesting bug!
It wasn't in mod_blog per se,
but in the hook running after an entry has been added,
and therein is the bug—the entry was successfully added,
but the hook failed.
The hook program failed due to a compilation error that was only triggered when it ran.
I took the email notification code from mod_blog and turned it into a program.
I also linked to the bloging core of mod_blog to avoid having to duplicate the code to read the configuration
(the email notification block is now ignored by mod_blog itself),
and because the configuration format is Lua,
a compiler option is needed to support Lua modules written in C—basically, -rdynamic to allow C-based Lua modules to call Lua functions
(which I allow, and need, to support my particular configuration).
This is the root cause of the issue.
But in the meantime,
because the hook failed to run,
the script I use that uses the HTTP PUT method received a status of “500 Internal Server Error,”
the entry was stored,
but none of the statically generated files
(index.html and the various feed files)
were generated,
nor email sent.
Once I figured out what happened, it was easily remedied, but that still leaves the question of what should happen? I intended the add entry post-hook to handle situations like notifications, so in this case, if the hook fails, normal processing should proceed, but how to send back that the entry post-hook failed? Looking over the HTTP status codes, perhaps I could return a “202 Accepted” when the entry post-hook fails, with some information about the failure. That could work.
The fix wasn't easy, or C precedence bites
For the past decade now,
I've done a Christmas release for mod_blog
(only missing the years 2019 and 2023),
just beacuse.
I was poking around the codebase looking for changes I could make for Christmas this year,
and well,
I got a bit impatient and have just now released a version in time for Halloween.
And it's scary—I'm removing features!
I've removed features in the past, like no longer supporting “ping servers” when it became clear it wasn't worth it, or automatically updating LinkedTikFaceMyInstaPinMeGramWeTokInBookSpaceTrest when InstaPinTikMyLinkedFaceMeTrestBookGramWeInSpaceTok changed how it works often enough to make it annoying for me to continue. But this time … this time it's different. This is removing functionality that has existed in the code base since the beginning!
To make it easier to write entries,
I had code within mod_blog to process the input—mostly what existed was to convert sequences like ``quoted'' to “quoted” and “...” to “…”,
but with an option to add <P> tags around logical paragraphs.
But given that I now use my own markup language,
I rarely used the web interface
(like, I can count on my fingers the number of times I've used it and still have a few left over should give an indication of how little I use it)
and the code just sat there,
unused.
So unused that in fixing one bug I introduced another bug in the code I fixed!
To recap, here's the original code:
char UrlDecodeChar(char **psrc)
{
char *src;
char c;
assert(psrc != NULL);
assert(*psrc != NULL);
src = *psrc;
c = *src++;
if (c == '+')
c = ' ';
else if (c == '%')
{
assert(isxdigit(*src));
assert(isxdigit(*(src+1)));
c = ctohex(*src) * 16 + ctohex(*(src+1));
src += 2;
}
*psrc = src;
return(c);
}
and the “fixed” version:
char UrlDecodeChar(char **psrc)
{
char *src;
char c;
assert(psrc != NULL);
assert(*psrc != NULL);
src = *psrc;
c = *src++;
if (c == '+')
c = ' ';
else if (c == '%')
{
if (!isxdigit(*src)) return '\0';
if (!isxdigit(*src+1)) return '\0';
c = ctohex(*src) * 16 + ctohex(*(src+1));
src += 2;
}
*psrc = src;
return(c);
}
I don't fault you if you can't spot the bug.
I only found it when testing the web interface to ensure it wasn't completely broken with the conversion code removed
(instead it's now only mostly broken but that's an interesting case in and of itself and requires its own post).
The bug is in this line of code:
if (!isxdigit(*src+1)) return '\0';
The issue is due to C's precedence rules and dereferencing rules.
The code above is parsed as src[0] + 1 instead of the src[1] that I was intending.
When I modified the function,
changing the calls to assert() into actual code to return an error
(I typed in the new code as that's faster than modifying the existing code)
I … kind of missed that.
Oh, who am I kidding? I totally missed that. But because I don't use the web interface this bug went unnoticed. Sigh.
For the record, I changed the code to read:
if (!isxdigit(src[0])) return '\0'; if (!isxdigit(src[1])) return '\0'; c = ctohex(src[0]) * 16 + ctohex(src[1]);
Another long time feature I've removed is email notification. I added it early on where you could submit your email address to get notified of posts, but spammers and a lack of outside interest pretty much put the kibosh on that. As I still have three users of the email notification (one is me, one is Bunny, and one is one other person whom I'm not sure still reads the emails but they haven't bounced yet) I don't want to drop support completely, so now the email notifications are sent via the hook mechanism I added a few years ago.
In total,
I removed over 3,000 lines of code from mod_blog.
Granted,
over 2,000 of them were in one function that was removed,
but still,
it's 3,000 lines of code I don't have to worry about any more.
Still, it's a bit scary to remove features that have been there for so long, and thus, a Halloween release.
Discussions about this entry
- The fix wasn't easy, or C precedence bites - Lemmy: Bestiverse
- The fix wasn't easy, or C precedence bites | Hacker News
Thursday, October 16, 2025
So account deletion at Network Solutions is a bit more nuanced than I was led to believe
I was, perhaps, a bit harsh with my criticisms of account deletion at Network Solutions. I noticed that they had yet to remove the billing information, so I called back to get an update and this time I was able to talk to a “billing specialist.” They still had to manually delete the credit card info (which they did while I waited) but I also learned a bit more about their policies about account deletion. If there's no services on an account, it will be deleted after a month (the “billing speciallist” wasn't exactly clear on this, but it sounded like a month) of inactivity, which, okay, I can see that. I just wish that was a bit more visible on the site, both to reassure those that want to leave Network Solutions, and to warn those that do use Network Solutions that no billing activity on their account can lead to automatic deletion.
So they move up from “clown show” to “annoying to use.”
Monday, October 13, 2025
I don't think it's news to anyone out there that one should avoid Network Solutions for domain registration and probably for anything else as well
The rest of my domains have been transfered away from Network Solutions. The process wasn't hard. It wasn't even really tedious, it just took a bunch of waiting.
I would log into Network Solutions, click past a bunch of needless notifications and upsells, and request a domain transfer. I would then get a chance to renew the domain for the low-low price of $19.95, which technically is cheaper, but still twice the price that my new registar, Porkbun, charges. Click past that, and I would have to wait up to four days in order to change my mind before Network Solutions send the transfer key. You know, to keep me from making a rash decision to stop paying them money.
Once I got the transfer key, I would then transfer in the domain to Porkbun. Network Solutions would then send an email 24 hours later, informing me that I have four days to change my mind, but I should talk to one of their “transfer specialists” to help transfer my domain, because Network Solutions is adamant that I don't rush into transfering my domains away from them and thus, stop paying them.
Four days after that, I would receive email from both Network Solutions and Porkbun that the domain (or domains actually) transfered over. So the process was mostly a waiting game on the part of Network Solutions.
Now that I'm no longer using Network Solutions for domain registration,
I want to delete my account there.
Of course,
there's no link on the Network Solutions to delete my account,
you know,
to keep me from making a rash decision.
Nope,
I have to call to talk to an “account specialist” to do that deed.
And it turns out,
there is no way for them to delete my account.
None.
Nada.
Zip.
Let that sink in—there is no way to delete your Network Solutions account!
They're damn adamant that I keep my account, just in case!
The best I can do is delete my credit card information. You know, the same credit card information that you can't update what-so-ever. In reality, they have to manually delete the credit card information from my “from now until the Heat Death of the Universe” account at Network Solutions.
Good Lord. What a clown show!
Update on Thursday, October 16th, 2025
Account deletion at Network Solutions is a bit more nuanced than I thought.
![Glasses. Titanium, not steel. [Self-portrait with my new glasses]](https://www.conman.org/people/spc/about/2025/0925.t.jpg)