Monday, May 29, 2006

SqueakNOS project

It looks like the effort to make Squeak into an operating system has started again. Check out SqueakNOS; they have a bootable Live CD for x86 with VESA graphics, and mouse and keyboard input.

Saturday, May 27, 2006

Window configuration (and content) now saved in the image

I implemented a useful new feature. When you start the UI it no longer resets all state, instead it tries to pick up where it left off. This was easy to implement. First, I made it so that window positions are stored in the 'world-loc' slot of the world tuple associated to the window. Next, I made the UI startup routine test if the windows hash is non-empty; and if so, it would iterate over the hashtable's values, and open windows with the gadgets stored therein.

This doesn't work perfectly yet; for example, the stacking order of the windows is not saved. However this is easy to fix.

Adding a feature like this would be difficult to a language which was not image-based. The key thing is that everything is saved. You can open a browser window, resize it, scroll the panes, open a listener, push some values on the stack, save the image, close Factor, re-open it, and your values are still on the stack and everything is as you left it. You can even cause a parse error in the listener, save the image, re-open the image, and invoke a restart as if nothing had happened!

Of course, I/O streams, aliens and other objects will expire, so its not quite as flawless as I described above; but in most cases, it Just Works.

Wednesday, May 24, 2006

Linspire Linux distribution standardizes on Haskell

From Chris Double's web log: the Linspire Linux distro is standardizing on Haskell. It would be interesting to see a well-designed language used in place of the mess of Perl/Python/bash scripts that infest a Linux distro.

Cocoa services support

I've implemented Cocoa services support for Mac OS X. If you drop your Factor.app into an Applications directory, then next time you log in, there will be a Factor submenu in the Services menu in each application.

Screenshots demonstrate this feature best.

So we launch TextEdit, and type a short Factor expression...

Then we invoke the "Evaluate Selection" service...

And the result is pasted right back into the TextEdit window:

One problem is that if Factor is not running, invoking the service does not launch it. I'm not sure how to fix this; I guess LaunchServices cannot locate the Factor.app for some reason.

I also implemented support for open events, so dropping a file from the Finder onto the Factor dock icon will open a listener window and run the file.

Better handling of undefined words; restarts

Everybody who tries Factor has run into this situation:
  \ layout see
An unhandled error was caught:

Parsing <interactive>:1
\ layout see
^
"Not a number"

:s :r :c show stacks at time of error
:get ( var -- value ) accesses variables at time of error
:error starts the inspector with the error
:cc starts the inspector with the error continuation

Oops! You forgot to use the right vocabulary -- if you know which one, otherwise its another call to apropos:
  USE: gadgets-layouts
\ layout see
IN: gadgets-layouts : layout
dup gadget-relayout? [
f over set-gadget-relayout? dup layout* dup
layout-children
] when drop ;

This is somewhat irritating, and the error message ("Not a number") is not helpful at all. It arises because of how the Factor parser works; first it looks up each token in the vocabulary search path, and if it does not find a word named by this token, it attempts to parse it as a number.

Now after my latest changes, this situation is handled more gracefully:
  \ layout see
An unhandled error was caught:

Parsing :1
\ layout see
^
"No word named layout found in current vocabulary search path"

The following restarts are available:
0 :res Use the word IN: gadgets-layouts : layout
:s :r :c show stacks at time of error
:get ( var -- value ) accesses variables at time of error
:error starts the inspector with the error
:cc starts the inspector with the error continuation
0 :res
IN: gadgets-layouts : layout
dup gadget-relayout? [
f over set-gadget-relayout? dup layout* dup
layout-children
] when drop ;

After the restart is invoked, the relevant vocabulary is automatically added to the search path, and parsing continues. If more than one word with the given name is defined in the dictionary, a number of restarts are offered, one for each possible vocabulary.

Here is how it works behind the scenes:

The condition ( error restarts -- restart ) word signals a recoverable error. The error parameter is the actual error message; it is wrapped inside a special condition object, together with the restarts parameter (more on it in a second) and the current continuation. The restarts parameter is an association list, associating a string description of a restart to an object. If the user chooses to restart from this error, the object associated with the chosen restart will be pushed on the stack, and execution will resume after the point where condition was called.

Here is an example:
: restart-test
"This is broken" {
{ "Indeed" t }
{ "I disagree" f }
} condition
"You choose " write . ;
restart-test

Running this example will yield the following error:

An unhandled error was caught:

"This is broken"

The following restarts are available:
0 :res Indeed
1 :res I disagree
:s :r :c show stacks at time of error
:get ( var -- value ) accesses variables at time of error
:error starts the inspector with the error
:cc starts the inspector with the error continuation

Invoking either restart using 0 :res or 1 :res will rewind execution and push a boolean true or false on the stack. The portion of the word definition after the call to condition now executes; it prints the received boolean on the stack.
  0 :res
You choose t

The problem with this approach is that any exception handlers further up the chain might have already closed streams and other external resources before the restart is invoked. In this case the restart will not function properly. What CL does is not execute any clean up handlers before the user picks a restart; this involves two trips up the catch stack, one to collect restarts and possibly present a debugger, and another one to call the relevant cleanup hooks, if any, depending on what restart is chosen. I'll investigate this further and pick a good solution. In any case I don't intend to use this feature as extensively as CL uses restarts, so I hope to avoid hairy situations.

Saturday, May 20, 2006

Some fun with the UI

I implemented two new useful tools in the UI. First up is an apropos tool:

Next up, a multi-pane browser, inspired by the Whisker browser for Squeak; the idea here is that in each column, you can click on several items which are all shown at once in the next column:

Both tools are in the DARCS repository and will be part of Factor 0.83.

If you are interested, here is the source code:

Thursday, May 18, 2006

Quotations now a first-class type; conses removed

I finished pushing a large set of patches to the main repository which implement the changeover I've been talking about for a while now.

What follows is a rundown of what has changed.

First of all, literals like [ 1 2 3 ] are now instances of a quotation type. They implement the sequence protocol and behave much like arrays. Any sequence can be converted to a quotation via >quotation. The difference between a quotation and an array is that like the list-based "quotations" of old, these objects can be passed to the call and if primitives which are the basic building blocks of all higher order functions in Factor.

It used to be that f and [ ] both parsed to the same object; this is now no longer the case. The object [ ] is a length-zero quotation.

Parsing words will need to be changed. To begin a new nested parse level, push f not [ ]. To add to the parse tree, use parsed instead of swons. The end result is a vector, not a list in reverse order. Replace reverse calls with >quotation or similar. If this terse description made no sense, it will be more detailed in the documentation.

A lot of code did not use the list-specific words, but it used list literals to hold data. Using [ ] literals for non-code data is still okay. I'd prefer if people used arrays { } for that purpose, it just makes the code a tiny bit more clear.