Sat, 23 Aug 2008

Looks like Emacs 22 has a --script flag that allows you to use it to
write #! scripts.  So here's a kinda ugly command-line-callable elisp
script to colorize a file with Emacs font-lock.

Like everything else posted to kragen-hacks without any notice to the
contrary, this program is in the public domain; I abandon any
copyright in it.

#!/usr/local/bin/emacs --script
;;; environment setup

(require 'cl)

;; It's too bad there's no convenient way to set EMACSLOADPATH in my
;; environment.
(push "/usr/share/emacs/site-lisp" load-path)
(push "/usr/share/emacs/site-lisp/autoconf" load-path)
(push "/usr/share/emacs/site-lisp/bigloo-ude" load-path)
(push "/usr/share/emacs/site-lisp/dictionaries-common" load-path)
(push "/usr/share/emacs/site-lisp/emacs-goodies-el" load-path)
(push "/usr/share/emacs/site-lisp/erlang" load-path)
(push "/usr/share/emacs/site-lisp/ess" load-path)
(push "/usr/share/emacs/site-lisp/gambc" load-path)
(push "/usr/share/emacs/site-lisp/haskell-mode" load-path)
(push "/usr/share/emacs/site-lisp/mmm-mode" load-path)
(push "/usr/share/emacs/site-lisp/octave2.1-emacsen" load-path)
(push "/usr/share/emacs/site-lisp/php-elisp" load-path)
(push "/usr/share/emacs/site-lisp/pymacs" load-path)
(push "/usr/share/emacs/site-lisp/pymacs-elisp" load-path)
(push "/usr/share/emacs/site-lisp/python-mode" load-path)
(push "/usr/share/emacs/site-lisp/ruby1.8-elisp" load-path)
(push "/usr/share/emacs/site-lisp/tuareg-mode" load-path)

(require 'htmlize)

(find-file "/home/kragen/devel/mystrlen.s") ; could just pass this on the command line
(message "ok")
(font-lock-fontify-buffer)              ; not happening automatically
(message "fontified")
(switch-to-buffer (htmlize-buffer))     ; note that htmlize-file would probably be better...
(message "htmlized")
(write-file "mystrlen.s.html")
(message "wrote")

Sat, 16 Aug 2008

This little Perl script outputs a TSV file containing the language
codes and names of various different Wikipedias.  It depends on
HTML::TableExtract and libwww-perl, both of which are free software
available from CPAN.

I used Perl for this rather than Python because I didn't know of an
equivalent to HTML::TableExtract in Python.  It turns out that someone
wrote something intended to be a Python equivalent of
HTML::TableExtract called TableParse, back in 2003, but I haven't
tried it.

Like everything else posted to kragen-hacks without any notice to the
contrary, this program is in the public domain; I abandon any
copyright in it.

#!/usr/bin/perl -w
use strict;
use HTML::TableExtract;
use LWP::Simple qw(get);

my $html;
my $fn = '/home/kragen/docs/List_of_Wikipedias';
if (open my $file, '<', $fn) {
  $html = do { local $/; <$file> };
} else {
  my $url = 'http://meta.wikimedia.org/wiki/List_of_Wikipedias';
  warn "Couldn't open $fn: $! --- trying $url";
  $html = get $url;
}

my $te = HTML::TableExtract->new( headers => [qw(Language Wiki)] );
# the rest of this is more or less from `perldoc HTML::TableExtract`
$te->parse($html);

foreach my $ts ($te->tables) {
  print "Table (", join(',', $ts->coords), "):\n";
  foreach my $row ($ts->rows) {
    print "\t", join("\t", @$row), "\n";
  }
}

Sat, 09 Aug 2008

	## simple illustration of the circle-drawing trick from HAKMEM
        ## in the form of a 64-byte MS-DOS .COM file
	## by Kragen Javier Sitaker, 2008, public domain
	## don't expect too much --- this is not going to win any prizes.
        ## it's not in the same league visually as dirojed or klappquadrat.
        ## but hey, it's my first 64-byte intro, I don't know what I'm doing.

	## compile with gas as follows:
        ## as -R morecircles.s -o morecircles.o
        ## objcopy -O binary morecircles.o morecircles.com.0
        ## dd if=morecircles.com.0 of=morecircles.com bs=256 skip=1
	
	## I've only tested it in DOSBox and in QEMU with FreeDOS;
	## hope it works elsewhere!

        ## registers are used as follows:
        ## %ax: X-coordinate from center of circle, sin theta
        ## %bx: scratch space
        ## %cx: loop counter
        ## %dx: Y-coordinate from center of circle, cos theta
        ## %di: scratch space used to compute address of pixel
        ## %si: center of circle (as pixel address)
        ## %bp: PRNG state
        ## %cs, %ds, %ss, %sp, %ip: the usual
	## %es: the video RAM segment
	## the top stack item: the current color and a counter

        .code16
        .org 0x100
	## set video mode 13h
start:  mov $0x13, %al
        int $0x10
        ## load video segment into %es
        #mov $seg-2, %bx # if we leave this out, it actually still mostly works
        les (%bx), %bp # we don't care what goes into %bp as long as it's not 0

init:   ## change radius and position pseudorandomly
	mov %bp, %ax            # random seed is in %bp
        imulw (start+1)         # 0xcd13**n % 65536 has period 16384
        mov %ax, %bp

        xor %ax, %si            # get some "randomness" into the position
	xor %dx, %dx            # set angle to 90 degrees
        mov %ax, %cx            # number of iterations: (scaled) radius

loop:   ## turn coordinates in %ax, %dx into offset in %di
        ## first, put %dx/256 * 320 into %di
	## sin and cos are scaled by 16 so the circle is round, not octagonal
	xor %bx, %bx
        mov %dh, %bh
        mov %bx, %di
	sar $2, %bx
        add %bx, %di

        ## now add x-coordinate, %ax/256
	mov %ax, %bx
        sar $8, %bx
        add %bx, %di

        ## now offset them both by the center of circle
        add %si, %di
        #mov %si, %di    # to see the centers only, to see how bad the RNG is

	## now set a pixel
        pop %bx                 # we're using whatever was on the stack...
        movb %bh, %es:(%di)     # change color every 256 pixels by using %bh
        inc %bx
        push %bx

	## this is the circle-drawing trick from HAKMEM
        ## to update sin and cos:
	## cos += sin * scale
        mov %ax, %bx
        sar $4, %bx
        add %bx, %dx
        ## sin -= cos * scale
        mov %dx, %bx
        sar $4, %bx
        sub %bx, %ax
        
        loop loop
        ## now that we've drawn a circle, let's go to the next one
	jmp init
	
#seg:    .short 0xa000  # (see above about "it actually still mostly works")

## In case you have trouble compiling it:	
## kragen at thrifty:~/devel/circles$ xxd < morecircles.com
## 0000000: b013 cd10 c42f 89e8 f72e 0101 89c5 31c6  ...../........1.
## 0000010: 31d2 89c1 31db 88f7 89df c1fb 0201 df89  1...1...........
## 0000020: c3c1 fb08 01df 01f7 5b26 883d 4353 89c3  ........[&.=CS..
## 0000030: c1fb 0401 da89 d3c1 fb04 29d8 e2d6 ebc6  ..........).....

Sat, 02 Aug 2008

(The programs in this post will be available in
<http://canonical.org/~kragen/sw/win32>, including in compiled form,
when I get around to uploading them, probably before this post goes
out on kragen-hacks.  Like everything else posted to kragen-hacks
without a notice to the contrary, these programs are in the public
domain.)

Debian Etch has both Wine, to run Win32 programs, and MinGW32 3.4.5,
to create them (although it's missing the MinGW GDB, so there's no
debugger).  I've been toying with that lately.  Here are some things I
learned:

1. Wine is remarkably compatible with the proprietary implementations
   of Win32.
2. By default MinGW compiles things for the "Windows CUI" subsystem,
   which makes a console window pop up when you run them (on Windows
   XP but not in Wine); you can see this problem with `winedump dump
   foo.exe` and fix it by telling the linker `--subsystem 0x2`.
3. MinGW puts "stabs" debugging information into the "PE"-format
   executables it creates, but `winedbg --gdb` doesn't know how to use
   stabs in PE; it's for debugging Wine, not debugging Win32 programs
   running in Wine.  As a consequence, running GDB with `winedbg
   --gdb` to debug Win32 programs is more or less comparable to
   running GDB with `target remote localhost:1234` to debug MS-DOS
   programs in QEMU.
4. Win32 is a pretty ugly API, more or less comparable to Xlib.  I
   sort of knew that before.  For example, I used to have a Win32
   equivalent of `perror` somewhere (something like 10 lines to
   interpret `GetLastError`) but I can't find it or figure out how to
   reimplement it.  (There's a nice list in `winesrc/include/winerror.h`
   though.)
5. Wine has really fantastic debugging dumps when programs crash.  You
   can debug a lot of problems faster by compiling a crashing C
   program with MinGW, and running it under Wine, and looking at the
   debug dump, than you often can by compiling it normally and running
   it under GDB.
6. PE files have an "entrypoint RVA" in the PE header that tells them
   where to start running.
7. Wine and MinGW32 do not include any significant documentation of
   Win32.  However, the Wine source code is really helpful, even
   though it's 1.2-1.7 million lines of code, about the size of the
   Encyclopedia Britannica, which can make it hard to find things
   sometimes.  `etags` helps a lot with that.
8. Most Win32 API tutorials online are really terrible, full of errors
   and badly written.  The exception is theForger's:
   <http://www.winprog.org/tutorial/>
9. Win32 debugging information is gigantic.  `strip` can reduce a
   600kB program to less than 8kB.
10. Wine crashes when run without a stack size ulimit.  GNU Make
    removes the stack size ulimit that is normally present.
    Consequently, programs run in Wine crash when run from a Makefile
    unless you do something like `bash -c "ulimit -s 8192; $(WINE)
    $<"` in the Makefile to run them.

Anyway, I wrote this hello-world assembly program, based on Jeff
Huang's Windows Assembly Programming Tutorial:

	## little "hello, world" bare Win32 program in gas
        ## I compile it with Debian Etch's MinGW32 3.4.5.20060117 as
	## follows:
	## i586-mingw32msvc-gcc -g -Wall -nostdlib w32hello3.S \
	##     -lkernel32 -luser32 -Wl,--subsystem,0x2 -o w32hello3.exe
        MB_OK = 0
        .data
hello:  .asciz "hello, world"
        .text
        push $MB_OK
        push $hello             # title
        push $hello             # contents
        push $0                 # parent window handle
	# I don't know why it's @16 and not @4 or something.  Maybe
	# it's the number of bytes it pops off the stack when it
	# returns?
        call _MessageBoxA at 16    # ASCII version, not Wide
        push $0
        call _ExitProcess at 4


I also worked through theForger's fantastic Win32-in-C programming
tutorial and wrote some simple C programs.  They seem to work on the
Windows XP machines in the nearby locutorio as well as in Wine on my
laptop.  Here's a slow, but pretty, graphics hack, based in part on
Farbrausch's "fr-016: bytes"; the original version is multiple files,
but I've turned it into a single file for the purpose of this post.

#include <windows.h>
#include <stdio.h>

// will probably only compile with MinGW GCC
// compile as follows:
// i586-mingw32msvc-gcc -Wall circ.c -Wl,--subsystem,0x2 -o circ.exe -lgdi32

typedef struct {
  PWNDCLASSEX class;
  HWND hWnd;
  char *title;
  int style;
  int x, y;
} window_instance;

// call this to initialize a WNDCLASSEX and a window_instance.
// doesn't register the window class.  do that yourself.
void init_window_classex(PWNDCLASSEX class,    // pointer to uninitialized class
                         window_instance *win, // pointer to uninitialized inst
                         HINSTANCE hInstance,  // of the application
                         WNDPROC WndProc,
                         LPCSTR name);         // of the window class

// call this to actually instantiate a window handle
// returns 0 on failure
int create_window(window_instance *win, int width, int height);

// for things that you want to MessageBox about and exit if they fail
#define check(foo) pcheck(foo, int)
#define pcheck(foo, type) (type)_check(#foo, (int)foo)
int _check(char *contents, int result);


void init_window_classex(PWNDCLASSEX class,
                         window_instance *win,
                         HINSTANCE hInstance,
                         WNDPROC WndProc,
                         LPCSTR name)
{
  WNDCLASSEX mine = {    // What a fucking pain in the ass.
    // never changing:
    .cbSize = sizeof(WNDCLASSEX),
    .style = 0,
    .cbClsExtra = 0,
    .cbWndExtra = 0,

    // supplied by user:
    .lpfnWndProc = WndProc,
    .hInstance = hInstance,
    .lpszClassName = name,

    // sensible defaults:
    .hIcon = LoadIcon(NULL, IDI_APPLICATION),
    .hCursor = LoadCursor(NULL, IDC_ARROW),
    .hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH),
    .lpszMenuName = NULL,
    .hIconSm = LoadIcon(NULL, IDI_WINLOGO),
  };

  *class = mine;
  win->class = class;
  win->hWnd = NULL;
  win->title = "Anonymous coward window";  // default value
  win->style = WS_OVERLAPPEDWINDOW;
  win->x = win->y = CW_USEDEFAULT;
}

int create_window(window_instance *win, int width, int height)
{
  win->hWnd = CreateWindow(win->class->lpszClassName,
                           win->title,
                           win->style,
                           win->x, win->y,
                           width, height,
                           NULL,    // parent handle
                           NULL,    // menu handle
                           win->class->hInstance,
                           NULL);
  return !!win->hWnd;
}

int _check(char *contents, int result)
{
  char errcodespace[32];
  if (result) return result;
  MessageBox(NULL, contents, "fatal _check error: this code failed", MB_OK);
  sprintf(errcodespace, "error code %d", (int)GetLastError());
  MessageBox(NULL, errcodespace, "here's why", MB_OK);
  ExitProcess(1);
}


int counter = 0;
enum { maxsize = 505 };
int y = 0, mousex = 0, mousey = 0;

void draw_in(HWND hWnd) {
  HDC hDC = pcheck(GetDC(hWnd), HDC);

  // double buffer one scan line
  HDC hDCBuffer = pcheck(CreateCompatibleDC(hDC), HDC);
  HBITMAP hbmBuffer = pcheck(CreateCompatibleBitmap(hDC, maxsize, 1), HBITMAP);
  HBITMAP hbmOldBuffer = pcheck(SelectObject(hDCBuffer, hbmBuffer), HBITMAP);

  int x;
  for (x = 0; x < maxsize; x++) {
    int dx = x - mousex;
    int dy = y - mousey;
    // YES this is a slow way of doing it; less than a megapixel per
    // second on my laptop.  Double buffering doesn't help much.
    // Sometimes, apparently at random, this call returns false (in
    // WINE).  I don't know why.  So I don't check the result.
    SetPixel(hDCBuffer, x, 0, (COLORREF)(dx*dx + dy*dy + counter));
    counter = (counter + 1) & 0xffffff;
  }

  check(BitBlt(hDC,  0, y, maxsize, 1,  hDCBuffer, 0, 0,  SRCCOPY));

  y = (y + 1) % maxsize;

  check(SelectObject(hDCBuffer, hbmOldBuffer));
  check(DeleteObject(hbmBuffer));
  check(DeleteDC(hDCBuffer));
  check(ReleaseDC(hWnd, hDC));
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) 
{
  switch(msg) {
  case WM_MOUSEMOVE: 
    mousex = LOWORD(lParam);
    mousey = HIWORD(lParam);
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    return DefWindowProc(hWnd, msg, wParam, lParam);
  }
  return 0;
}

int my_message_loop(window_instance *win)
{
  MSG msg;
  for (;;) {
    // PM_NOYIELD means don't block.
    int rv = PeekMessage(&msg, NULL, 0, 0, PM_NOYIELD);
    if (!rv) draw_in(win->hWnd);  // no messages pending
    else if (msg.message == WM_QUIT) return msg.wParam;
    else {
      // I hope this second PeekMessage is guaranteed to work, and get
      // the same message.
      check(PeekMessage(&msg, NULL, 0, 0, PM_NOYIELD | PM_REMOVE));
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine,
                     int nCmdShow)
{
  WNDCLASSEX windowClass;
  window_instance win;

  init_window_classex(&windowClass, &win, hInstance, WndProc, "AClass");
  windowClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
  check(RegisterClassEx(&windowClass));

  win.title = "hello, world";
  check(create_window(&win, maxsize, maxsize));
  ShowWindow(win.hWnd, nCmdShow);

  return my_message_loop(&win);
}