*(Here I should put in a disclaimer: the rumor is that _why is fine, and all he wanted was some peace and quiet away from internet fame.)
Today I want to talk about Yown. Yown is a webserver that _why wrote in Io. Except, it's not just that. It's also a clever practical joke (while still being a functional web server!) I'm the annoying sort who tells a joke and immediately explains, "See, that's funny because..." so if you don't want to read any ***spoilers***, stop now, go pick up a copy of Yown on your own and just start digging in the code; try to figure out how it works. If you're so motivated.
Now, for all those unmotivated folks whose answer to the above was, "let's not and say we did," here's what you would have learned.
First, a quick explanation of what the program does. Like ASP or PHP, you write something that is like a template and Yown builds a complete HTML page from the template to send to a browser. It comes with an example file:
//
// The most trivial Yown app
//
doRelativeFile("../yown.io")
YownSimple := Yown clone do(
get("/test",
html(
title("Hello??")
h1("THIS IS A TEST")
)
)
) run
This loads the Yown.io app, creates a clone of the Yown proto, hands it a page definition, and then calls the run method. We see some stuff in there that looks like HTML, but it doesn't use the angle brackets or closing tags. We see html, title, h1. What happens if we add a line, 'strong("is this line in bold?")'? It does what you'd expect: it puts <strong></strong> tags around that line of text.
Let me be quite clear: the example above was also Io code, not just a page template. It contains some things that appear to be method calls, but we'll find later that there's some funny business going on too.
Hint: to run these examples
(assuming you already have git and Io)
git clone http://github.com/whymirror/yown.git
cd yown
io samples/simple.io
Connect Firefox or another browser to:
http://localhost:8010/test
Then, View Source
So far so good.
When I was investigating Yown for the first time, I next tried making links. This seemed like a reasonable guess:
//
doRelativeFile("../yown.io")
YownSimple := Yown clone do(
get("/test",
html(
title("Hello??")
a("www.google.com", "This is a link")
)
)
) run
The result? Oh, too bad: www.google.com is displayed as text, the second argument is dropped, and there's no link. The HTML source for the page looks like this: <html><title>Hello??</title><a>www.google.com</a></html>. Odd: Yown seems to have eaten that "a" very matter-of-factly. Where's href=""? Let's just find the methods that create these tags; maybe it will show us what the syntax is and we might even be able to add links if they aren't supported.
Here's the file from Yown that applies:
//
// Yown Builder
// html construction kit
//
Builder := Object clone
Builder tag := method(name, nodes,
inner := ""
attrs := name split(".")
if (attrs size > 1,
name := attrs at(0)
attrs := " class='" .. attrs slice(1) join(" ") .. "'",
attrs := ""
)
while(nodes,
if(nodes name != ";",
inner = inner .. if(nodes argCount > 0,
tag(nodes name, nodes argAt(0)),
doMessage(nodes))
)
nodes = nodes next
)
"<#{name}#{attrs}>#{inner}</#{name}>" interpolate
)
Builder forward := method(
tag(call message name, call message argAt(0))
)
Oookay. Where's 'title()'? How about 'strong()'? Or h1? A href=?
Gaaaah! There's nothing here! What-? How-?
You can grep the Yown directory all you want. The methods are not there.
And that my friends is why Yown is so funny: it's like a man walking on stilts without any stilts. _why is playing a magic trick on us.
OK, but how does it work?
Take a close look at that last Builder method, see it, the one called 'forward'? What that does is any message (aka method call) that the object doesn't understand gets routed to the forward method. In the forward method, _why is using the name of the method as the HTML tag. (The part that says 'call message name'.) You could pass 'gobblegobble("hello!")' and Yown would turn it into '<gobblegobble>hello</gobblegobble>'!
There are some things you should know about forward(). It's very powerful. Too powerful. Anything you try to define afterwards using := will go to forward() instead. So it will probably be the last slot you define on a particular object. Note that this includes assigning from within the forward() method itself; if you try to use 'self someName := someValue' within forward, it will go into infinite recursion. Use 'self someName ::= nil' before the forward method exists, and then you can safely use 'setSomeName(someValue)' within forward.
Finally, note that anything handled by the object as a method natively will not go on to the forward method. That is obvious from the description of what foward() does, but if you were creating a domain specific language and used forward() for it, it might easily slip your mind when you try to add a keyword to the DSL that happens to already be defined for Io objects. There are alternative ways of creating DSLs using Io reflection that I will cover in a future post.
In the particular case of Yown, forward() is probably the most appropriate technique: it would allow you to define other methods (such as an 'a' method) for builder to handle special cases. If you decide to try extending Yown in this way, note that you'll need to change the inner loop of the Builder 'tag()' method to resend nested messages with arguments back to Builder itself.