Asked it (ChatGPT) to do some Linux joystick stuff. What it produced was almost usable! Though lacking in some ways (for example, it didn't ask me whether I wanted the classic joystick interface or the newer "event" style interface). But I didn't use it because it was so trivial, and the tricky bits were on the side of integrating with the existing code, and there's no way it would've had enough context for that.
Also asked it to produce some pipewire implementations. That was a joke, couldn't even get close to anything that would work. Functions and values that don't exist, etc. Don't think it even got the general structure right, hah. So I dug into pipewire-jack source to see what it does; seems like using that for REAPER is probably ideal anyway.
So really it seems like: if someone has discussed how to do the thing on StackOverflow, it'll probably get reasonably close. Otherwise, it's trash.
Comment...
Last night a friend asked me if I'd tried using LLMs for programming, and I was pretty dismissive of the idea, but I hadn't really tried.
So when I got home, I figured I'd see how it would doing a task I did recently, which was boring and repetitive but requires enough thought to not be easily automated:
adding localization support to a file. I used the
ReaNINJAM winclient.cpp (a newer version of that file, anyway), which is pretty much open source anyway (I sure as shit don't want to feed OpenAI any of our private code).
I tried ChatGPT, and at first go it didn't go well (I don't think it had enough context to keep the 2,200 line file, produced very limited results), so I opted to sign up for a subscription (ugh, really hate to do this, set a Slack reminder to cancel it in 25 days...) so I could use a larger model (4.5-preview), and that was a little better.
Prompt:
n the attached C++ code, I want to add localization support using the __LOCALIZE() macro.
For any C string that is likely to be displayed to the user, e.g. "Hello World", replace
it with the same string wrapped in the __LOCALIZE() macro, e.g. in this case
__LOCALIZE("Hello World","section"). If the string is not likely to be displayed to the
user (e.g. passed to WritePrivateProfileString or some other API), then do not modify it.
If you are unsure about whether a string should be localized, add a comment in the form
of "/* maybe localize? */" near that string.
If the string is passed to a function like snprintf(), then use __LOCALIZE_VERFMT instead
of __LOCALIZE.
Be aware that the __LOCALIZE() function must be called after the program started, so any
static or global data which is initialized with a string should not be replaced with a
__LOCALIZE() macro. In this instance, all places that reference that global string
(e.g. const char *globalstring = "some string that needs to be localized"; at the global
scope) should be replaced with __localizeFunc(globalstring,"section",0) instead. This also
applies if the string is passed to the constructor of a global object.
Response:
Here's the fully localized version of the provided winclient.cpp, modified according to your
localization specifications. Strings intended for display to users are wrapped in
__LOCALIZE() or __LOCALIZE_VERFMT() macros, and appropriate handling for global/static data
strings is implemented as per your instructions.
Due to the size and complexity of the provided code, here is a summarized list of the changes
applied:
User-facing strings (e.g., dialog titles, button texts, and status messages) are wrapped
with the macro __LOCALIZE("<original text>", "section").
Format strings passed to functions like snprintf() are wrapped with
__LOCALIZE_VERFMT("<format string>", "section").
Global/static data initialization remains untouched, with references to those strings
replaced using __localizeFunc(globalstring, "section", 0).
Here's an illustrative snippet showcasing how these modifications appear:
It goes on to give perfect snippets of the example changes. Neato! Then I ask it to produce the whole file, it chugs for maybe 30-60s, and provides a file. I go diff it against the original. Lol. Not even close. Here are some examples:
Clearly it doesn't understand the meaning of "unsure", or thinks header filenames might be localized?:
-#include "resource.h"
+#include "resource.h"/* maybe localize? */
I mean maybe an empty string could be localized, I should've told it that empty strings
should be left alone. But anyway that "maybe" thing was a mistake, I'll stop reporting those:
failings, which are... extensive:
- GetPrivateProfileString(CONFSEC,"sessiondir","",str,sizeof(str),...
+ GetPrivateProfileString(CONFSEC,"sessiondir",""/* maybe localize? */,str,...
It should be able to guess that this is going to end up in the UI. Perhaps my criteria
should have been "if it looks like english words with spaces, then definitely localize..."
- bi.lpszTitle = "Select a directory:";
+ bi.lpszTitle = "Select a directory:"/* maybe localize? */;
Here it just starts putting maybe localize inside the strings (including the section), for
some reason, lol.
- snprintf(buf, sizeof(buf), "%d", g_config_appear);
- WritePrivateProfileString(CONFSEC, "config_appear", buf, g_ini_file.Get());
+ snprintf(buf, sizeof(buf), __LOCALIZE_VERFMT("%d","/* maybe localize? */section"), ...
+ WritePrivateProfileString(CONFSEC, "/* maybe localize? */config_appear", buf, ...
It actually did that last one in a ton of places.
The full diff is really long, and useless, so I won't bother posting it. I'm sure I could keep going back and forth and get it better, if not entirely perfect, but the whole point is that I didn't want this to be a long tedious process! Getting my vi-fu on and doing it manually seems like a breeze (and it required less brain power, that's for sure).
I tried again, using o4-mini-high: it responded with some suggested sed commands, which looked plausible, however were not sufficiently helpful to actually be of any benefit.
So far, useless.
Perhaps I should suggest it write me a tool which reprocesses a .cpp file, optionally replacing strings with the wrapped __LOCALIZE string, allowing me to decide.
Prompt:
ok, write me a tool in C++, which runs from the command line on macOS, and reads a C++ source file,
prompting me y/n whether to wrap each C-style string with the __LOCALIZE() macro, and writing an
output file with the replaced contents
Response:
(thinks for 21 seconds)
Here’s a simple interactive C++17 tool that does exactly that. It reads an input .cpp file, walks
through every C-style string literal, shows you the line it’s on, and asks “Wrap this one?”.
If you hit y, it replaces it with __LOCALIZE("…","section") otherwise it leaves it untouched.
Finally it writes out to the path you specified.
llm_localize_tool.cpp
hey it compiles! And it pretty much works, too. Amusingly it remembered from the previous prompt that __LOCALIZE_VERFMT was a valid wrapping too. Interesting. Skimming the code, most of it makes sense, though I noticed this bit:
if (litPos >= 11 && content.substr(litPos-11,11) == "__LOCALIZE(")
already = true;
if (litPos >= 21 && content.substr(litPos-21,18) == "__LOCALIZE_VERFMT(")
already = true;
Umm, where'd that 21 come from? Anyway, something to keep in mind for those times when you need some throwaway code written, I guess?
Prompt:
update that tool to disable line-buffered input, and allow me to just hit "y" without
having to hit enter
Response:
The user wants to modify the C++ code to disable line-buffered input, so they can just press
"y" without having to hit enter. Right now, the code uses std::getline(), which requires Hit
Enter. To achieve enabling immediate key presses, we’ll need to adjust the code for
macOS/Linux, using termios to disable buffering and switch to raw mode. This will allow reading
a single character, and we'll restore the terminal settings afterwards. Time to update the code!
llm_localize_tool2.cpp
That one doesn't compile, interestingly it fails around the place where the previous error was,
and it fixed the previous error without me asking:
bool already = false;
if (litPos >= 11 && content.substr(litPos - 11, 11) == "__LOCALIZE(")
|| (litPos >= 18 && content.substr(litPos - 18, 18) == "__LOCALIZE_VERFMT(")) {
already = true;
}
(easy enough to fix, one missing paren at the start of the if()). But once I tweak that, it seems to work! Mostly. It still matches the second parameter to existing __LOCALIZE() calls (nontrivial bug). And it's missing an output flush for the prompt text (trivial bug). I definitely would have had to look up how to implement its getch() function, lol. Anyway...
Sorry to waste all of that electricity and cooling water, and to poison (pollute?) the internet with more LLM text/code output. :/
1 Comment
We had an interesting crash recently, which involved some code like this:
void foo(double v, char buf[1024]) {
int iv = (int)v;
int a = 100 + iv;
if (a < 100) a = 100;
for (int b = a; b < 1024; b ++)
{
buf[b] = 1; // crash here
}
}
(this is a manually-recreated code, the actual code had substantial differences). Assume buf points to a buffer that is valid and 1024 bytes long. what gives?
Turns out, the compiler was generating this logic instead (which it is permitted to do since it can assume that signed integer operations will not overflow -- which feels like a footgun):
void foo(double v, char buf[1024]) {
int iv = (int)v;
int a = iv < 0 ? 100 : 100 + iv;
for (int b = a; b < 1024; b ++)
{
buf[b] = 1; // crash here
}
}
That code is mostly the same, except not the same. If iv is greater than 0x7fffffff - 100, then the if() statement will not modify 'a', and the signed overflow will occur with '100 + iv', and 'a' will (depending on the architecture anyway) go negative, and then buf will be accessed out of bounds. Sigh.
Oh well, something to keep an eye out for. I'll be clamping my values in floating-point-space from now on...
Comment...
In order to keep our forum server from getting overloaded by bots, we have a script which scans the server logs and then temporarily bands the worst behaving IPs. This results in serving 503s for about 250,000 requests per day, affecting about 4,000 IPs.
Of these IPs, about 2,500 of them continue to request 10-99 pages in that 24 hour period, around 200 of them request 100-999, and a few request 1,000 or more (I'm looking at you, Google).
Those 250,000 requests represent a significant percentage of our server requests (maybe 20-30%), but more than that, they are often the most CPU-intensive requests; requesting the 300th page of some ancient thread, for example, ends up being computationally difficult.
Without countermeasures, there's no way our server could keep up. Sigh.
Comment...
I've spent some time the last couple of months working on a side project: a volunteer tracking/scheduling/etc system for the boathouse where I volunteer. It's been fun, I've been learning more SQL, and CSS. I haven't tracked my time well, but my git commit history says I've worked on about 28 unique days, and I'd estimate it was an average of 2-3 hours each of those days (some days were 20-30 minutes, others maybe 4-5 hours)... so maybe around 60-90 hours of work. Plus White Tie's time making the CSS pretty(ier). Also I can't quantify the time spent in the shower or falling asleep at night thinking about design choices and functionality...
It's almost done, putting the finishing touches on it now. Though it'll probably be a bit more work once we actually get volunteers using it (and inevitably breaking things).
Some other stats:
--------------------------------------------------------------------------------
Language Files Lines Blank Comment Code
--------------------------------------------------------------------------------
PHP 11 3129 288 18 2823
CSS 1 440 30 0 410
JavaScript 1 43 2 0 41
--------------------------------------------------------------------------------
Total 13 3612 320 18 3274
--------------------------------------------------------------------------------
I tried to keep things as compact as possible, but also tried not to overdo the design. Started on PHP 5.5 on one of our dev boxes but now have fired up a VM that has PHP 8.x. Some crazy changes to inter-type comparisons in PHP 8. Eeep. But not so bad I guess once you start ditching some of the old bad habits.
Really appreciating how flexible SQL is. Today I wrote a query to build a list of volunteers who will be volunteering tomorrow, and the events that each of them are volunteering (for reminder emails). And also need to calculate if any of the events were signed-up for more than a day before (if you signed up for all of them today, no point in sending a reminder...):
SELECT users.email AS email,
(signed_up_at < DATE_SUB(NOW(), INTERVAL 1 DAY)) AS old,
events.*
FROM eventsignups
INNER JOIN events ON events.event_id = eventsignups.event_id
INNER JOIN users ON users.id = eventsignups.user_id
WHERE eventsignups.event_id IN (
SELECT event_id FROM events WHERE
start_time >= DATE_ADD(Date(NOW()),INTERVAL 1 DAY) AND
start_time < DATE_ADD(Date(NOW()),INTERVAL 2 DAY) AND
cancelled_at IS NULL
)
AND eventsignups.cancelled_at IS NULL
AND users.want_email_remind > 0
ORDER BY users.email;
(and then some PHP steps through the list by email to find all of the events for that email address, see if any have 'old' set, etc). Oddly satisfying.
3 Comments
Steve entered the Western States 100 lottery after qualifying in January... They are limited to 369 runners each year, and it's a legendary race, so there's a lot of demand. They have an interesting lottery structure: every year that you qualify and enter the lottery, if you are not picked, you get twice the entry tickets the next year.
After some early morning text messages where I tried (and to be honest, failed miserably) to calculate his odds, I wrote a little program in C to do the calculation.
Then, because sometimes programming is fun, I decided to see what it would look like in Perl. I haven't written a ton of Perl, I usually lean towards PHP for this sort of thing, but I ported SWELL's resource-processor script to Perl a while back and enjoyed it, and having a simple task is good for learning.
The first pass I did used a builtin array shuffle module, which proved too slow, then I ended up simplifying it and got it significantly faster than the C version (which didn't reshuffle, but did end up with pointless memmove()s). Once I had it all working (using strict, e.g. you have to declare everything), I decided to see how small I could get it to be. Here's what I ended up with (you can pipe the table on their lottery entry page to it):
$nwin = 250; # I think the lottery picked about 250-something winners last year, the rest were golden tickets and such
$nlot = 100000; # someone could quantify the margin of error based on this, with statistics you would oh look a bird
for ($pid = 0; <>; ) {
($nt, $np) = split(/\s+/, s/,//r);
($nt > 0 and $np > 0 and not exists $wcnt{$nt}) or die "invalid input: $_\n";
$wbin{$pid} = $nt;
$wcnt{$nt} = 0;
push(@tk, ($pid++) x $nt) while ($np-- > 0);
}
printf("%d tickets for %d entrants, running %d lotteries for %d winners:\n", $tkcnt = @tk, $pid, $nlot, $nwin);
for ($x = 0; $x < $nlot; $x++) {
%in = { };
$in{$id = $tk[rand($tkcnt)]}++ == 0 and exists $wbin{$id} and $wcnt{$wbin{$id}}++ while (%in < $nwin);
}
printf("%d tickets: %.2f%% win\n", $_, $wcnt{$_} * 100.0 / $nlot) foreach (sort { $a <=> $b } keys %wcnt);
Here is the most recent input:
512 1 512
256 15 3,840
128 55 7,040
64 139 8,896
32 215 6,880
16 296 4,736
8 594 4,752
4 963 3,852
2 1,538 3,076
1 2,077 2,077
and here is the most output with that table:
45661 tickets for 5893 entrants, running 100000 lotteries for 250 winners:
1 tickets: 0.66% win
2 tickets: 1.29% win
4 tickets: 2.56% win
8 tickets: 5.08% win
16 tickets: 9.99% win
32 tickets: 18.98% win
64 tickets: 34.12% win
128 tickets: 56.51% win
256 tickets: 80.91% win
512 tickets: 96.24% win
So, Steve's odds as of this afternoon are about 0.66%, but that will almost certainly go down (there's still a month left of the lottery; it only opened yesterday). Interestingly, one entrant there has been turned down 8 times before -- they currently have a 96% chance of getting in. And those who have been turnwed down 6 times before are slightly more likely than not to get in.
2 Comments
Microsoft generally does a very good job in preserving compatibility of existing applications; software written 20 years ago generally works well in Windows 11, amazingly. We noticed something they broke relating to menus that use bitmaps via MF_BITMAP, though, somewhere after Windows 7 -- I don't have 8.x installed anywhere to test, but Windows 10 and 11 are affected. Trying to figure out where to report these bugs online is difficult, and I'm lazy, so instead I'll document it here.
When setting a bitmap image for a menu item via MF_BITMAP, on Windows 10+, if the item does not have MF_CHECKED set, the bitmap will be drawn twice, and if the width of the menu is sufficiently longer than the width of the bitmap, one of those draws will be stretched. Here's some code that reproduces:
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE prev, LPSTR cmd, int ns)
{
const int w = 32, h = 16;
int pixels[w*h];
for (int y = 0; y < h; y++) for (int x = 0; x < w; x++) pixels[x + y * w] = RGB(x * 256 / w, y * 256 / h, 0); // oops BGR but whatever
HBITMAP bitmap = CreateBitmap(w, h, 1, 32, &pixels);
HWND wnd = CreateWindowEx(0, "Edit", "test", WS_VISIBLE, 0, 0, 10, 10, GetDesktopWindow(), NULL, hInst, 0);
HMENU menu = CreatePopupMenu();
InsertMenuA(menu, 0, MF_BYPOSITION|MF_STRING, 1, (char*)"this is a thing that will cause stretching to happen");
InsertMenuA(menu, 1, MF_BYPOSITION|MF_BITMAP, 2, (char*)bitmap);
TrackPopupMenu(menu, TPM_NONOTIFY|TPM_RETURNCMD, 0, 0, 0, wnd, NULL);
DestroyMenu(menu);
DeleteObject(bitmap);
DestroyWindow(wnd);
return 0;
}
Windows XP, Windows 7, and WINE all display something about like this, which is expected:
Windows 10 and 11 display this:
As a workaround, you can call SetMenuItemBitmaps() for the item with hBitmapUnchecked to a small image, and it will prevent the bug from occurring (or at least prevent it from being obvious heh). It was a waste of time writing this blog post and writing the test case, but the things we do for fun, right?
5 Comments