It didn't surprise me, but some people might wonder how I could have a bunch of bugs left in the parsing code, when I had pretty complete test coverage.
It wasn't because the bugs were in the few lines that weren't executed by the tests.
The problem is that just executing every line doesn't means you've tested all the possible "paths" through the code. And it certainly doesn't mean you've tested every possible input.
For something like the parser with many branches (if's and loop's) and deeply recursive, it's pretty much impossible to test every possible "path".
I still think it's worthwhile aiming for good test coverage. If you don't have good coverage that means there are parts of your code that haven't ever been run by your tests. And we all hate the bugs that seem so blatant that people say "didn't you run this?".
So just don't let good test coverage give you a false sense of security. There can still be lots of bugs hiding in those untested execution paths.
Friday, February 27, 2009
Thursday, February 26, 2009
Another jSuneido Milestone
jSuneido now parses Suneido code, at least the 11 mb of source code that I threw at it.
Note: I'm only checking that it parse successfully - I'm not checking the results of the parse so there could still be problems. For example, in ambiguous cases it might be picking the wrong choice. But I don't think it's too likely because the wrong choices almost always lead to later syntax errors.
It didn't take long to set up the framework to read cSuneido dump files since I already had done this for loading stdlib into the database.
I initially expected a lot of failures but I was still a little surprised when it failed to parse a single record from stdlib! But I was still fresh and the sun was shining (although it was -30!) so I could laugh.
I soon found it was because all the stdlib records start with a copyright comment. Of course, none of my tests had an initial comment. This was easy to fix.
After that it was just slogging, fixing errors one by one. Sometimes programming seems like mountain climbing. Finishing a project and getting to the top are great, but you'd better enjoy the process as well, since that's where you spend most of your time.
I was really hoping to get the parsing working by the end of the day, but by late afternoon it seemed like I might not make it. Then I fixed one more bug and ... no more errors! It threw me off for a minute. I'd grown so accustomed to the error output that it took me a minute to realize it had succeeded.
I wish I could say I was done with parsing, but I still have to go back and rewrite the query parsing to not use Antlr. Oh well, that shouldn't take too long.
Then on to actually generating Java byte code! I'm going to try using ASM to start. It looks promising and even has an Eclipse plugin, but if it doesn't work out, there are other alternatives.
Note: I'm only checking that it parse successfully - I'm not checking the results of the parse so there could still be problems. For example, in ambiguous cases it might be picking the wrong choice. But I don't think it's too likely because the wrong choices almost always lead to later syntax errors.
It didn't take long to set up the framework to read cSuneido dump files since I already had done this for loading stdlib into the database.
I initially expected a lot of failures but I was still a little surprised when it failed to parse a single record from stdlib! But I was still fresh and the sun was shining (although it was -30!) so I could laugh.
I soon found it was because all the stdlib records start with a copyright comment. Of course, none of my tests had an initial comment. This was easy to fix.
After that it was just slogging, fixing errors one by one. Sometimes programming seems like mountain climbing. Finishing a project and getting to the top are great, but you'd better enjoy the process as well, since that's where you spend most of your time.
I was really hoping to get the parsing working by the end of the day, but by late afternoon it seemed like I might not make it. Then I fixed one more bug and ... no more errors! It threw me off for a minute. I'd grown so accustomed to the error output that it took me a minute to realize it had succeeded.
I wish I could say I was done with parsing, but I still have to go back and rewrite the query parsing to not use Antlr. Oh well, that shouldn't take too long.
Then on to actually generating Java byte code! I'm going to try using ASM to start. It looks promising and even has an Eclipse plugin, but if it doesn't work out, there are other alternatives.
Wednesday, February 25, 2009
jSuneido Progress
I pretty much have the parsing port finished. I think of Suneido as a "small" language, but it's big enough when you're trying to parse it! I have over 90% test coverage (as reported by EclEmma). The only parts that aren't well tested are the error handling. I should probably add a few more tests for that.
The tests are more "functional" than "unit" tests. If something broke they wouldn't be much help locating the exact source of the problem. But I think it would be hard to write true unit tests for something like a recursive descent parser. I'm happy enough with the functional tests.
There are probably a few things I've missed, especially to do with optional semicolons. Next I plan to try parsing all our Suneido source code. That should flush out any remaining issues.
I also want to go back and rewrite the query parsing so I can eliminate the remaining dependencies on Antlr. This shouldn't be too hard. The lexical scanning is the same as the language, just with a different set of keywords, so I don't have to write that.
Then I'll be on to the fun challenge of actually generating Java byte code.
In the long run I'd prefer not to support two implementations of Suneido. Assuming jSuneido works out for the server, I'd like to move on to figuring out how to implement the client side. The hard part here is the UI. Currently, Suneido's UI calls the Win32 API directly.
One short term solution might be to look at whether Java can call the Win32 API. If so, this would allow a quick port, although it would limit the client to Windows.
Another obvious, but longer term, approach would be to rewrite the UI in Swing or SWT.
A less obvious, but interesting approach would be to go with a browser type approach using HTML, CSS, Javascript. This is a really messy, ugly approach, but it has some interesting properties. For example, it's the approach being taken by the new Palm webOS.
In any case, this is all speculation. I've still got plenty to do to get the server part of jSuneido working.
The tests are more "functional" than "unit" tests. If something broke they wouldn't be much help locating the exact source of the problem. But I think it would be hard to write true unit tests for something like a recursive descent parser. I'm happy enough with the functional tests.
There are probably a few things I've missed, especially to do with optional semicolons. Next I plan to try parsing all our Suneido source code. That should flush out any remaining issues.
I also want to go back and rewrite the query parsing so I can eliminate the remaining dependencies on Antlr. This shouldn't be too hard. The lexical scanning is the same as the language, just with a different set of keywords, so I don't have to write that.
Then I'll be on to the fun challenge of actually generating Java byte code.
In the long run I'd prefer not to support two implementations of Suneido. Assuming jSuneido works out for the server, I'd like to move on to figuring out how to implement the client side. The hard part here is the UI. Currently, Suneido's UI calls the Win32 API directly.
One short term solution might be to look at whether Java can call the Win32 API. If so, this would allow a quick port, although it would limit the client to Windows.
Another obvious, but longer term, approach would be to rewrite the UI in Swing or SWT.
A less obvious, but interesting approach would be to go with a browser type approach using HTML, CSS, Javascript. This is a really messy, ugly approach, but it has some interesting properties. For example, it's the approach being taken by the new Palm webOS.
In any case, this is all speculation. I've still got plenty to do to get the server part of jSuneido working.
Tuesday, February 24, 2009
Eclipse Tip for Windows and Mac Users
Switching back and forth between Windows and Mac, my fingers find it hard to remember whether copy/cut/paste/undo/redo are with the control key or the command key.
In Eclipse you can configure the keyboard so you can add the control versions of these (as well as the command versions).
I also added home and end (Line Start/End).
One nice feature of Windows under Parallels on the Mac is that it lets you use either command or control.
In Eclipse you can configure the keyboard so you can add the control versions of these (as well as the command versions).
I also added home and end (Line Start/End).
One nice feature of Windows under Parallels on the Mac is that it lets you use either command or control.
Sunday, February 22, 2009
There's Always a Better Way
I'm working on the parser for jSuneido and it's going well.
In a few places the parser needs to "look ahead" at future tokens.
In cSuneido I did this by looking ahead directly in the source string.
When I was porting this code it seemed a little ugly. For example, it handled skipping whitespace but not comments. (Meaning you couldn't put a comment in certain places in the code.)
My next thought was to save the state, scan ahead, then restore the state. Easy enough, especially since it didn't have to be nested.
But it still seemed a little ugly.
Then I realized, I could just clone the lexer, and read ahead in the clone without disturbing the original. (I didn't use the Java clone stuff since it's a little twisted. I just made a "copy" constructor that takes another lexer.)
I'm a lot happier with this solution. I bet there's an even better solution out there, but this one will do for now.
I think one of the big weaknesses in beginning programmers is that they tend to settle for the first thing that works. But there are always other solutions and the first one you come up with is unlikely to be the best.
Of course, this is also what refactoring is about - improving your code, not settling for the first draft.
The hard part in this porting is having to read all the old C++ code and see all kinds of ways it could be improved. But I have to resist refactoring the C++ code or I'll never make any progress on jSuneido. One thing at a time. It's enough of a challenge to make the Java code as clean as I can.
PS. I'm back to using Eclipse. I could be happy with NetBeans but I find Eclipse a little better. Of course, part of that is just that I've spent more time with Eclipse.
In a few places the parser needs to "look ahead" at future tokens.
In cSuneido I did this by looking ahead directly in the source string.
When I was porting this code it seemed a little ugly. For example, it handled skipping whitespace but not comments. (Meaning you couldn't put a comment in certain places in the code.)
My next thought was to save the state, scan ahead, then restore the state. Easy enough, especially since it didn't have to be nested.
But it still seemed a little ugly.
Then I realized, I could just clone the lexer, and read ahead in the clone without disturbing the original. (I didn't use the Java clone stuff since it's a little twisted. I just made a "copy" constructor that takes another lexer.)
I'm a lot happier with this solution. I bet there's an even better solution out there, but this one will do for now.
I think one of the big weaknesses in beginning programmers is that they tend to settle for the first thing that works. But there are always other solutions and the first one you come up with is unlikely to be the best.
Of course, this is also what refactoring is about - improving your code, not settling for the first draft.
The hard part in this porting is having to read all the old C++ code and see all kinds of ways it could be improved. But I have to resist refactoring the C++ code or I'll never make any progress on jSuneido. One thing at a time. It's enough of a challenge to make the Java code as clean as I can.
PS. I'm back to using Eclipse. I could be happy with NetBeans but I find Eclipse a little better. Of course, part of that is just that I've spent more time with Eclipse.
Thursday, February 19, 2009
NetBeans
In a totally unwarranted diversion from the job at hand I decided to have a "quick" look at NetBeans.
Download and install were easy and quick.
A minor quibble was that it asked me to register and I would have, but it just took me to a web site to create an account. No thanks, I've got better things to spend my time on than creating yet another account.
NetBeans can import Eclipse projects so I had my project open in no time.
I ran all my tests and ... one failed! It complained that I was using the wrong assertEquals to compare floating point numbers. I didn't actually want to compare floating point numbers - I had used Math.signum instead of Integer.signum. Easily fixed. Strange that Eclipse didn't catch that. Maybe a newer version of JUnit.
This was a good opportunity to use NetBeans' out-of-the-box Subversion. Or not. It told me my Subversion client was too old and refused to do anything. So much for out-of-the-box.
As with Eclipse, there appear to be different ways to connect to Subversion. One of them is with JavaHL- part of Subclipse. Another is through the command line client.
I updated my plugins but that didn't help.
I tested the command line client and it was indeed old. (Apple seems to like to stick to old versions.) One recommendation was to install a newer version from Collabnet. Which meant I had to sign up for a Collabnet account.
Then I had to give NetBeans the path to it. Now my Subversion appears to work. But again, like with Subclipse, it was definitely not as easy as it should be. Presumably every Mac + NetBeans + Subversion user (that hasn't manually upgraded their SVN client) is going to have the same problem.
Another minor quibble is that I didn't like the font in NetBeans (Monospaced) as much as the one in Eclipse (Monaco) but that's easily adjusted in the Preferences. Probably just a matter of what I'm used to.
So I start programming in NetBeans. I add a class, oops, meant to add a Junit test. Try to delete the class (that I just created) and it won't let me. The error message is ultra helpful "cannot delete file". But I can delete it fine from the Finder, and then it disapppears from NetBeans. Very strange but I'm not going to worry about it. (until the next time!)
One feature that I'm missing already is that NetBeans doesn't seem to offer to create missing methods like Eclipse does. This is great for "programming by intention" where you just write your code, calling functions that you haven't written yet, and then go back and create them. (NetBeans will automatically create unimplemented methods from interfaces.)
I do like how Find is a bar at the bottom of the editor, like Firefox, and what I recently added to Suneido. But Replace is still a dialog :-( as it is currently in Suneido (but I am planning to change Suneido's Replace to a bar as well)
Wow! I just realized that the HOME and END keys are working "properly" (i.e. beginning and end of line like Windows) in NetBeans! To get these working in Eclipse I had to customize the keyboard mapping. On the Mac HOME and END normally scroll to the top and bottom, but for programming or even just writing, I more often want to go to the beginning or end of the line.
Despite the messing around I managed to get a fair bit of the parser ported. I'm glad I'm separating the parsing from the code generation so I can work on (and test) one thing at a time. It's also turned out to be quite easy to split the parser into several parts (constants, expressions, statements). This should let me re-use the expression parser in queries. (Suneido's query expression have always been a subset of the language expressions because I never got around to doing everything in two places.)
I looked for a metrics plugin for NetBeans so I could see how many lines of code I'd written today (roughly 300 plus tests) but the only one I found needed to be built from source and I couldn't be bothered right now. There do seem to be more plugins for Eclipse than for NetBeans.
Download and install were easy and quick.
A minor quibble was that it asked me to register and I would have, but it just took me to a web site to create an account. No thanks, I've got better things to spend my time on than creating yet another account.
NetBeans can import Eclipse projects so I had my project open in no time.
I ran all my tests and ... one failed! It complained that I was using the wrong assertEquals to compare floating point numbers. I didn't actually want to compare floating point numbers - I had used Math.signum instead of Integer.signum. Easily fixed. Strange that Eclipse didn't catch that. Maybe a newer version of JUnit.
This was a good opportunity to use NetBeans' out-of-the-box Subversion. Or not. It told me my Subversion client was too old and refused to do anything. So much for out-of-the-box.
As with Eclipse, there appear to be different ways to connect to Subversion. One of them is with JavaHL
I updated my plugins but that didn't help.
I tested the command line client and it was indeed old. (Apple seems to like to stick to old versions.) One recommendation was to install a newer version from Collabnet. Which meant I had to sign up for a Collabnet account.
Then I had to give NetBeans the path to it. Now my Subversion appears to work. But again, like with Subclipse, it was definitely not as easy as it should be. Presumably every Mac + NetBeans + Subversion user (that hasn't manually upgraded their SVN client) is going to have the same problem.
Another minor quibble is that I didn't like the font in NetBeans (Monospaced) as much as the one in Eclipse (Monaco) but that's easily adjusted in the Preferences. Probably just a matter of what I'm used to.
So I start programming in NetBeans. I add a class, oops, meant to add a Junit test. Try to delete the class (that I just created) and it won't let me. The error message is ultra helpful "cannot delete file". But I can delete it fine from the Finder, and then it disapppears from NetBeans. Very strange but I'm not going to worry about it. (until the next time!)
One feature that I'm missing already is that NetBeans doesn't seem to offer to create missing methods like Eclipse does. This is great for "programming by intention" where you just write your code, calling functions that you haven't written yet, and then go back and create them. (NetBeans will automatically create unimplemented methods from interfaces.)
I do like how Find is a bar at the bottom of the editor, like Firefox, and what I recently added to Suneido. But Replace is still a dialog :-( as it is currently in Suneido (but I am planning to change Suneido's Replace to a bar as well)
Wow! I just realized that the HOME and END keys are working "properly" (i.e. beginning and end of line like Windows) in NetBeans! To get these working in Eclipse I had to customize the keyboard mapping. On the Mac HOME and END normally scroll to the top and bottom, but for programming or even just writing, I more often want to go to the beginning or end of the line.
Despite the messing around I managed to get a fair bit of the parser ported. I'm glad I'm separating the parsing from the code generation so I can work on (and test) one thing at a time. It's also turned out to be quite easy to split the parser into several parts (constants, expressions, statements). This should let me re-use the expression parser in queries. (Suneido's query expression have always been a subset of the language expressions because I never got around to doing everything in two places.)
I looked for a metrics plugin for NetBeans so I could see how many lines of code I'd written today (roughly 300 plus tests) but the only one I found needed to be built from source and I couldn't be bothered right now. There do seem to be more plugins for Eclipse than for NetBeans.
Eclipse + Subversion
One of the problems with third party add-on/plug-in architectures, like Eclipse or Firefox is that installing the software isn't enough, you also have to reinstall all your add-on's.
You would think that since this is such a common issue that there would be tools to ease this process but I haven't encountered any. You should at least be able to export a list of add-on's from one installation and import them into another. You can sync your bookmarks and passwords in Firefox (with add-ons) but you can't sync your add-on's.
After reinstalling Eclipse (eclipse-java-ganymede-SR1-macosx-carbon.tar), one of the first things I needed was access to Subversion. Eclipse comes with support for CVS but not Subversion, even though most people moved from CVS to SVN long ago, and are now moving from SVN to Git.
I've been using Subclipse but I thought I'd check if that was still the best option. I found there was also Subversive. It's came from Polarion but it's now a part of the Eclipse project so at first I thought that would be better. But a little research gave mixed results. There's obviously some squabbling between the two projects. Apparently Subclipse didn't join the Eclipse project due to license issuings. Subversive got around the licensing issues by not providing all the components, requiring you to separately install some parts.
The overall feeling I got from my quick research was that both plug-ins provide much the same functionality, but people seemed to have fewer problems with Subclipse. There are enough problems with both that some people recommend just using TortoiseSVN outside Eclipse. But TortoiseSVN is only available on Windows (where I do use it).
So I tried to install Subclipse. And got errors about dependencies. Sometimes I just have to shake my head at the state of our technology. This is a fresh install of Eclipse and a fresh install of Subclipse. You can't get much simpler than that. Wouldn't that be the first, most obvious test case? To be fair, maybe it's something to do with being on Mac OS X or something specific to my setup. You never know. But more research (what did we do before the internet and search engines?) showed that I wasn't the only one with this problem.
One suggested fix was to uncheck some of the optional components - that got rid of the errors.
When I had added Subclipse, it had automatically added SVNkit (from Eclipse itself). One of the optional components in Subclipse is the SVNkit adaptor. When you uncheck this, it unchecks the whole SVNkit plug-in. It appears that it's the SVNkit that "causes" the errors, not the optional Subclipse components.
The "best" part, is that when you proceed to the install, it appears that it is installing SVNkit after all - despite it being unchecked. I'm not sure what's going on, but it seems to work so I'm not going to waste any more time on it.
Here's a screen shot to illustrate:
Maybe I should be trying NetBeans. It doesn't have Antlr support, but now that I'm not using Antlr, that's not an issue. And it does have official support for Subversion!
You would think that since this is such a common issue that there would be tools to ease this process but I haven't encountered any. You should at least be able to export a list of add-on's from one installation and import them into another. You can sync your bookmarks and passwords in Firefox (with add-ons) but you can't sync your add-on's.
After reinstalling Eclipse (eclipse-java-ganymede-SR1-macosx-carbon.tar), one of the first things I needed was access to Subversion. Eclipse comes with support for CVS but not Subversion, even though most people moved from CVS to SVN long ago, and are now moving from SVN to Git.
I've been using Subclipse but I thought I'd check if that was still the best option. I found there was also Subversive. It's came from Polarion but it's now a part of the Eclipse project so at first I thought that would be better. But a little research gave mixed results. There's obviously some squabbling between the two projects. Apparently Subclipse didn't join the Eclipse project due to license issuings. Subversive got around the licensing issues by not providing all the components, requiring you to separately install some parts.
The overall feeling I got from my quick research was that both plug-ins provide much the same functionality, but people seemed to have fewer problems with Subclipse. There are enough problems with both that some people recommend just using TortoiseSVN outside Eclipse. But TortoiseSVN is only available on Windows (where I do use it).
So I tried to install Subclipse. And got errors about dependencies. Sometimes I just have to shake my head at the state of our technology. This is a fresh install of Eclipse and a fresh install of Subclipse. You can't get much simpler than that. Wouldn't that be the first, most obvious test case? To be fair, maybe it's something to do with being on Mac OS X or something specific to my setup. You never know. But more research (what did we do before the internet and search engines?) showed that I wasn't the only one with this problem.
One suggested fix was to uncheck some of the optional components - that got rid of the errors.
When I had added Subclipse, it had automatically added SVNkit (from Eclipse itself). One of the optional components in Subclipse is the SVNkit adaptor. When you uncheck this, it unchecks the whole SVNkit plug-in. It appears that it's the SVNkit that "causes" the errors, not the optional Subclipse components.
The "best" part, is that when you proceed to the install, it appears that it is installing SVNkit after all - despite it being unchecked. I'm not sure what's going on, but it seems to work so I'm not going to waste any more time on it.
Here's a screen shot to illustrate:
Maybe I should be trying NetBeans. It doesn't have Antlr support, but now that I'm not using Antlr, that's not an issue. And it does have official support for Subversion!
Monday, February 16, 2009
Computer Frustrations
Sometimes computers really piss me off.
I thought I was finally figuring out Antlr. Then my grammar worked in AntlrWorks, but not in Eclipse. AntlrWorks is using Antlr 3.1 but Eclipse was using Antlr 3.0. In the Eclipse preferences I see I can choose 3.1 but when I do, Antlr quits working. Ok, I guess I need to upgrade. But I expected this would be a problem because I haven't been able to update in Eclipse for quite a while. I try to update anyway, but it just tells me I can't, for some cryptic reason that doesn't help me at all.
This roadblock makes me think about whether I should be using Antlr. I've already spent quite a few days struggling with it and progress is very slow. I'm pretty sure I could have ported the C++ code to Java by now. One of the big reasons to use a tool like Antlr is to work at a higher level that's easier to modify. But the more I add to my grammar, the more brittle it seems to get. Small changes tend to break the whole thing with a cascade of vague messages.
In a way, it's understandable. I'm trying to port a grammar from a hand written recursive descent parser. In a hand written parser you can do all kinds of things that aren't possible in a more structured system like Antlr. And things like optional semicolons are not the easiest to handle with tools like Antlr.
The ironic part is that Antlr works great to do simple stuff (like Suneido's query language) but for more complex stuff it becomes increasingly difficult, at least if you're not an expert with it. Of course, that makes it almost useless - complex stuff is where you need it. If you were developing a little language from scratch it would be fine because you could just tailor the language to what Antlr handled easily.
I hate to give up, especially on an interesting challenge like this, but I have to remind myself that the goal is to port Suneido, not to spend endless hours struggling to learn and use some tool. And even if I got it to work, I'm then stuck with a dependency on Antlr. Which is already causing me problems and will just make it harder for anyone else who might want to build or modify jSuneido. And heaven forbid if they want to change the grammar - if they touch it, they're more than likely to break it, with no idea how to fix it.
To make a long story short, I'm giving up on Antlr and I'm going to have to start over with a fresh install of Eclipse in order to be able to update.
But that's just the beginning of my current computer frustrations. My irritation level is already quite high because of way too much obligatory family stuff this weekend. But before I started back on jSuneido I figured I'd better fulfil one last duty and download the photos Shelley took during the family stuff. Easy, just fire up Lightroom, import the photos, and copy them over to the file server so Shelley can look at them.
Wrong. Lightroom hangs up part way through the import. And not only hangs up Lightroom, but the whole of OS X. I end up having to force the power off and reboot. I thought it was because I was messing around while I was waiting for the import to finish. So I try again, this time not touching anything while it's working. It gets a little farther, but again it locks up the whole computer. WTF! I force the power off again and reboot. But this time it corrupted the Lightroom catalog and it can't repair it. Grrrrrr.
I have backups, but they don't include my last session in Lightroom because Lightroom backs up at the beginning of a session. That seems totally backwards to me. First, I want to backup what I do in a session. I may go weeks before I use it again, during which time my last session hasn't been backed up. Second, checking the catalog and backing up takes quite a while. Having to wait for that when I go to do something with Lightroom is really annoying, which means half the time I skip it, meaning my backups are liable to be even more out of date. I'd much rather it did the backup when I quit, since it wouldn't be holding me up and I wouldn't be so tempted to skip it.
And there's yet another frustration in this. My first instinct was to recover the file with Time Machine, since it's saved me a few times. I don't use it very often so I thought I was doing something wrong when I couldn't get it to work. Then I realized the Lightroom folder is inside the Pictures folder, and I don't back up the Pictures folder since it's so big. And I keep a mirror of it on the file server anyway so Shelley can access it from her Windows machine.
There also used to be problems with Time Machine having trouble backing up the Lightroom catalog when it was open, but I think/hope that's fixed. (???)
I start a Time Machine backup to get the Lightroom folder backed up. It takes forever to "prepare the backup" and then it says it's backing 16 gb. WTF again. There's no friggin way I have added / modified 16 gb of stuff since the last Time Machine backup. Then I realize that the Lightroom folder also contains the preview cache, which I see has grown to be 13 gb! Ok, stop the backup (which also takes forever, for some reason), exclude the previews, restart the backup, wait for it to "prepare",
So I move my Lightroom folder out of the Pictures folder so it will get backed up. I start up Lightroom, point it at the new catalog location, and ... it crashes. Now I'm getting really pissed off. I mess around with various ways of starting Lightroom, with various catalogs and eventually I get it running.
Meanwhile, half my morning is gone and I haven't accomplished a damn thing other than to raise my blood pressure. Sometimes you have to wonder about these "time saving" machines.
I still need to import the photos but I'm a little gun shy - I don't really want to crash my whole computer again. I was trying to import directly from the memory cards (the way I always do). Maybe something in the low level memory card reader drivers was the problem, so I copy the files on to the hard drive and try importing from there. Success!
Then I notice that a bunch of the file names have "-1" on the end. Argh! That's because I already imported a bunch of them. They weren't in the Lightroom catalog because I restored to an old version, but the actual files were still there. So now I have a bunch of duplicate files. (Lightroom normally prevents this, but only if they're in the catalog.)
Lightroom has a function to synchronize the catalog with a folder, so I use that. And ... it hangs. This is not my day. Lightroom normally runs very smoothly. Of course, I'm off the "happy path", and I'm thrashing around, which is always dangerous.
Maybe the Time Machine backup I've got running is interfering with Lightroom. It still hasn't finshed "preparing" but at least it stops fairly promptly when I tell it to.
I go to force quit Lightroom and a dialog shows up. ARGH! again. It wasn't hung up, it was just asking what I wanted to do with the file differences. Being reminded of my own stupidity doesn't help my mood. Besides, I need to synch after I clean up the duplicate files, not before.
I use a shell to rm *-1*. No easy way to do that from the Finder or from Lightroom. If you didn't know how to use the shell you'd be stuck with dragging files to the trash can one by one. GUI's aren't always so great.
I synch again, this time waiting for the dialog to pop up. Finally, I have the photos imported.
I start up ChronoSync and update the mirror of my photos on the file server. (I don't use its scheduler to do this automatically because it always seemed to want to run at the worst possible times and most of the runs did nothing because I hadn't imported anything new.)
Time for coffee. Then maybe I can actually do something productive. If you can call it "productive" to abandon multiple days of work with a sophisticated tool and go back to implementing by hand.
I thought I was finally figuring out Antlr. Then my grammar worked in AntlrWorks, but not in Eclipse. AntlrWorks is using Antlr 3.1 but Eclipse was using Antlr 3.0. In the Eclipse preferences I see I can choose 3.1 but when I do, Antlr quits working. Ok, I guess I need to upgrade. But I expected this would be a problem because I haven't been able to update in Eclipse for quite a while. I try to update anyway, but it just tells me I can't, for some cryptic reason that doesn't help me at all.
This roadblock makes me think about whether I should be using Antlr. I've already spent quite a few days struggling with it and progress is very slow. I'm pretty sure I could have ported the C++ code to Java by now. One of the big reasons to use a tool like Antlr is to work at a higher level that's easier to modify. But the more I add to my grammar, the more brittle it seems to get. Small changes tend to break the whole thing with a cascade of vague messages.
In a way, it's understandable. I'm trying to port a grammar from a hand written recursive descent parser. In a hand written parser you can do all kinds of things that aren't possible in a more structured system like Antlr. And things like optional semicolons are not the easiest to handle with tools like Antlr.
The ironic part is that Antlr works great to do simple stuff (like Suneido's query language) but for more complex stuff it becomes increasingly difficult, at least if you're not an expert with it. Of course, that makes it almost useless - complex stuff is where you need it. If you were developing a little language from scratch it would be fine because you could just tailor the language to what Antlr handled easily.
I hate to give up, especially on an interesting challenge like this, but I have to remind myself that the goal is to port Suneido, not to spend endless hours struggling to learn and use some tool. And even if I got it to work, I'm then stuck with a dependency on Antlr. Which is already causing me problems and will just make it harder for anyone else who might want to build or modify jSuneido. And heaven forbid if they want to change the grammar - if they touch it, they're more than likely to break it, with no idea how to fix it.
To make a long story short, I'm giving up on Antlr and I'm going to have to start over with a fresh install of Eclipse in order to be able to update.
But that's just the beginning of my current computer frustrations. My irritation level is already quite high because of way too much obligatory family stuff this weekend. But before I started back on jSuneido I figured I'd better fulfil one last duty and download the photos Shelley took during the family stuff. Easy, just fire up Lightroom, import the photos, and copy them over to the file server so Shelley can look at them.
Wrong. Lightroom hangs up part way through the import. And not only hangs up Lightroom, but the whole of OS X. I end up having to force the power off and reboot. I thought it was because I was messing around while I was waiting for the import to finish. So I try again, this time not touching anything while it's working. It gets a little farther, but again it locks up the whole computer. WTF! I force the power off again and reboot. But this time it corrupted the Lightroom catalog and it can't repair it. Grrrrrr.
I have backups, but they don't include my last session in Lightroom because Lightroom backs up at the beginning of a session. That seems totally backwards to me. First, I want to backup what I do in a session. I may go weeks before I use it again, during which time my last session hasn't been backed up. Second, checking the catalog and backing up takes quite a while. Having to wait for that when I go to do something with Lightroom is really annoying, which means half the time I skip it, meaning my backups are liable to be even more out of date. I'd much rather it did the backup when I quit, since it wouldn't be holding me up and I wouldn't be so tempted to skip it.
And there's yet another frustration in this. My first instinct was to recover the file with Time Machine, since it's saved me a few times. I don't use it very often so I thought I was doing something wrong when I couldn't get it to work. Then I realized the Lightroom folder is inside the Pictures folder, and I don't back up the Pictures folder since it's so big. And I keep a mirror of it on the file server anyway so Shelley can access it from her Windows machine.
There also used to be problems with Time Machine having trouble backing up the Lightroom catalog when it was open, but I think/hope that's fixed. (???)
I start a Time Machine backup to get the Lightroom folder backed up. It takes forever to "prepare the backup" and then it says it's backing 16 gb. WTF again. There's no friggin way I have added / modified 16 gb of stuff since the last Time Machine backup. Then I realize that the Lightroom folder also contains the preview cache, which I see has grown to be 13 gb! Ok, stop the backup (which also takes forever, for some reason), exclude the previews, restart the backup, wait for it to "prepare",
So I move my Lightroom folder out of the Pictures folder so it will get backed up. I start up Lightroom, point it at the new catalog location, and ... it crashes. Now I'm getting really pissed off. I mess around with various ways of starting Lightroom, with various catalogs and eventually I get it running.
Meanwhile, half my morning is gone and I haven't accomplished a damn thing other than to raise my blood pressure. Sometimes you have to wonder about these "time saving" machines.
I still need to import the photos but I'm a little gun shy - I don't really want to crash my whole computer again. I was trying to import directly from the memory cards (the way I always do). Maybe something in the low level memory card reader drivers was the problem, so I copy the files on to the hard drive and try importing from there. Success!
Then I notice that a bunch of the file names have "-1" on the end. Argh! That's because I already imported a bunch of them. They weren't in the Lightroom catalog because I restored to an old version, but the actual files were still there. So now I have a bunch of duplicate files. (Lightroom normally prevents this, but only if they're in the catalog.)
Lightroom has a function to synchronize the catalog with a folder, so I use that. And ... it hangs. This is not my day. Lightroom normally runs very smoothly. Of course, I'm off the "happy path", and I'm thrashing around, which is always dangerous.
Maybe the Time Machine backup I've got running is interfering with Lightroom. It still hasn't finshed "preparing" but at least it stops fairly promptly when I tell it to.
I go to force quit Lightroom and a dialog shows up. ARGH! again. It wasn't hung up, it was just asking what I wanted to do with the file differences. Being reminded of my own stupidity doesn't help my mood. Besides, I need to synch after I clean up the duplicate files, not before.
I use a shell to rm *-1*. No easy way to do that from the Finder or from Lightroom. If you didn't know how to use the shell you'd be stuck with dragging files to the trash can one by one. GUI's aren't always so great.
I synch again, this time waiting for the dialog to pop up. Finally, I have the photos imported.
I start up ChronoSync and update the mirror of my photos on the file server. (I don't use its scheduler to do this automatically because it always seemed to want to run at the worst possible times and most of the runs did nothing because I hadn't imported anything new.)
Time for coffee. Then maybe I can actually do something productive. If you can call it "productive" to abandon multiple days of work with a sophisticated tool and go back to implementing by hand.
Saturday, February 14, 2009
New Add-On System for Suneido
I just added an article on the Suneido web site about a new add-on system.
I'm currently reading Clean Code (recommended) so I was pleased with the small methods in the add-on code. However, I was half way through writing the code before I realized I wasn't writing tests, let alone tests first. Oops.
When the methods get so small I start to resent all the lines with just curly braces and start to wonder if a Python indentation based style might not be better suited.
I'm currently reading Clean Code (recommended) so I was pleased with the small methods in the add-on code. However, I was half way through writing the code before I realized I wasn't writing tests, let alone tests first. Oops.
When the methods get so small I start to resent all the lines with just curly braces and start to wonder if a Python indentation based style might not be better suited.
Thursday, February 12, 2009
jSuneido Antlr Parsing
More Antlr grammar fun.
I'm still struggling with optional semicolons. I thought I had it figured out but I had overlooked some cases. You can "view" semicolons three ways:
separators, as in "x ; y ; z"
terminators, as in "x; y; z;"
empty statements, as in "while (x) ;"
I had them working as separators, but that didn't handle "x;".
That was easy enough to fix, but then it didn't handle "while (x) ;" And it treated "x;" as two statements "x" followed by an empty statement, which isn't what I want. (Although it might not matter for code generation.)
Taking the terminator approach, I want statements to end with either a newline or a semicolon, unless they are followed by a "}" or an else, or several other cases. Yuck.
Also, reading the Antlr book more, it seems that just turning on backtracking globally may not be the best approach since you no longer get any warnings and it can potentially make the parser very slow. The alternative is to rewrite the grammar to avoid the ambiguities, turn on backtracking selectively on certain rules, or add your own syntactic predicates (instead of letting backtracking add them automatically).
To further confuse things, the book recommends ignoring some warnings (such as the classic if-then-else ambiguity) rather than fixing them with syntactic predicates since the default resolution is the correct one and the fix is less efficient. But that goes against the normal recommendation to eliminate all warnings, otherwise you may miss valid warnings mixed in with the ones you're supposed to be ignoring. The other problem is that I'm not sure I know how to figure out whether the Antlr default is what I want.
I removed the backtrack option and got rid of all but one ambiguity by tweaking the grammar. It was complaining that conditional expressions were ambiguous. At first I thought it was the if-then-else problem, but because the ":" part isn't optional like "else", that's not the problem. I found if I removed my assignment rule it solved the problem. Aha, the ambiguity is probably between "(x = y) ? ..." and "x = (y ? ...". You wouldn't have this problem if assignments were statements rather than expressions.
But it still seems a little odd, because that seems like a precedence issue, which in top-down grammars is handled by the order of nesting of rules. Wait a minute, assignment has the lowest precedence, so it should be first. But I had it last because that's how cSuneido had it. Now my grammar compiles cleanly with no warnings.
[I hadn't used the AntlrWorks IDE for a while, but I started using it again to experiment.]
It appears I have an approach that will handle the optional semicolon issue. [source] Before I complete the grammar, there are a few other questions I'd like to answer.
cSuneido emits byte code directly during the parse; it doesn't build a tree first. I think I should be able to do the same thing with the Antlr parser. The advantage of this approach is performance. The disadvantage is that you don't have a tree to use for other purposes. It's also hard to test since the only output is byte code which isn't human readable. You have to translate it to a form of assembly language to get something readable.
I'd like to decouple the parser from the code generator in a way that would allow me to attach a tree builder instead of the code generator. That will keep the performance advantages of direct code generation but still let me get a tree. For testing I could write something that will just produce strings.
This sounds simple, but it's somewhat complicated because generating code requires actions at intermediate points within the rules. e.g. "if" requires outputting a conditional branch between the expression and the statement. On the other hand, building a tree requires returning partial tree results from rules.
Antlr has a built in system for building abstract syntax trees. It would be great if I could use this since it would simplify the code a lot. But when I'm generating byte code I don't want to actually generate a tree. I wonder if I could write my own Tree class that would generate byte code but not actually build a tree. I'd then have to augment this with the extra intermediate actions. Unfortunately, the Tree interface is somewhat complicated. The BaseTree that is described as having no "payload" still keeps a list of its children, which I want to avoid when generating byte code.
Despite the extra work, I think it may be simpler to roll my own rather than try to make the tree stuff work in ways it wasn't intended to.
I modified Language.g to call a Builder interface and then implemented a StringBuilder used for ParseTest. So far so good. I still have to add the intermediate actions but then shouldn't be too hard. Then I can start to implement a Builder that does code generation.
ARGH! Rereading what I had written, I realize I didn't solve the empty statement issue. For example, it can't parse "if (x) ;" I guess that's what I'll have to work on next!
I'm still struggling with optional semicolons. I thought I had it figured out but I had overlooked some cases. You can "view" semicolons three ways:
separators, as in "x ; y ; z"
terminators, as in "x; y; z;"
empty statements, as in "while (x) ;"
I had them working as separators, but that didn't handle "x;".
That was easy enough to fix, but then it didn't handle "while (x) ;" And it treated "x;" as two statements "x" followed by an empty statement, which isn't what I want. (Although it might not matter for code generation.)
Taking the terminator approach, I want statements to end with either a newline or a semicolon, unless they are followed by a "}" or an else, or several other cases. Yuck.
Also, reading the Antlr book more, it seems that just turning on backtracking globally may not be the best approach since you no longer get any warnings and it can potentially make the parser very slow. The alternative is to rewrite the grammar to avoid the ambiguities, turn on backtracking selectively on certain rules, or add your own syntactic predicates (instead of letting backtracking add them automatically).
To further confuse things, the book recommends ignoring some warnings (such as the classic if-then-else ambiguity) rather than fixing them with syntactic predicates since the default resolution is the correct one and the fix is less efficient. But that goes against the normal recommendation to eliminate all warnings, otherwise you may miss valid warnings mixed in with the ones you're supposed to be ignoring. The other problem is that I'm not sure I know how to figure out whether the Antlr default is what I want.
I removed the backtrack option and got rid of all but one ambiguity by tweaking the grammar. It was complaining that conditional expressions were ambiguous. At first I thought it was the if-then-else problem, but because the ":" part isn't optional like "else", that's not the problem. I found if I removed my assignment rule it solved the problem. Aha, the ambiguity is probably between "(x = y) ? ..." and "x = (y ? ...". You wouldn't have this problem if assignments were statements rather than expressions.
But it still seems a little odd, because that seems like a precedence issue, which in top-down grammars is handled by the order of nesting of rules. Wait a minute, assignment has the lowest precedence, so it should be first. But I had it last because that's how cSuneido had it. Now my grammar compiles cleanly with no warnings.
[I hadn't used the AntlrWorks IDE for a while, but I started using it again to experiment.]
It appears I have an approach that will handle the optional semicolon issue. [source] Before I complete the grammar, there are a few other questions I'd like to answer.
cSuneido emits byte code directly during the parse; it doesn't build a tree first. I think I should be able to do the same thing with the Antlr parser. The advantage of this approach is performance. The disadvantage is that you don't have a tree to use for other purposes. It's also hard to test since the only output is byte code which isn't human readable. You have to translate it to a form of assembly language to get something readable.
I'd like to decouple the parser from the code generator in a way that would allow me to attach a tree builder instead of the code generator. That will keep the performance advantages of direct code generation but still let me get a tree. For testing I could write something that will just produce strings.
This sounds simple, but it's somewhat complicated because generating code requires actions at intermediate points within the rules. e.g. "if" requires outputting a conditional branch between the expression and the statement. On the other hand, building a tree requires returning partial tree results from rules.
Antlr has a built in system for building abstract syntax trees. It would be great if I could use this since it would simplify the code a lot. But when I'm generating byte code I don't want to actually generate a tree. I wonder if I could write my own Tree class that would generate byte code but not actually build a tree. I'd then have to augment this with the extra intermediate actions. Unfortunately, the Tree interface is somewhat complicated. The BaseTree that is described as having no "payload" still keeps a list of its children, which I want to avoid when generating byte code.
Despite the extra work, I think it may be simpler to roll my own rather than try to make the tree stuff work in ways it wasn't intended to.
I modified Language.g to call a Builder interface and then implemented a StringBuilder used for ParseTest. So far so good. I still have to add the intermediate actions but then shouldn't be too hard. Then I can start to implement a Builder that does code generation.
ARGH! Rereading what I had written, I realize I didn't solve the empty statement issue. For example, it can't parse "if (x) ;" I guess that's what I'll have to work on next!
Tuesday, February 10, 2009
jSuneido, Java, and Antlr
Back to working on jSuneido. I'm writing the Antlr lexer/parser for the Suneido language. In the process I'm discovering a few issues.
The return value from my language parser was wrapped in another class (constant_return). But that's not the case in my query parser - it returns the result value directly. Obviously the grammars are different but I can't see what would cause this difference. I dug around trying to find an answer but I gave up. If I wanted to spend the time on it I could start cutting down the grammars until I found what was doing it by a process of elimination. But it's easier to just extract the value out of the class. [Later: the problem went away when I added more rules.]
I wasn't handling explicit signs on numeric constants properly. I had an optional minus sign on the lexical rule for numbers, but that only worked if there was appropriate whitespace. "a - 5" was ok, but "a-5" got interpreted as "a -5" (no operator) and gave an error.
The fix led to ambiguities between whether "-5" was a numeric constant or an expression with a unary minus operator. I wanted it to be a numeric constant so I had to change my expression rules a bit.
I notice I don't seem to allow a plus sign on numeric constants in cSuneido. It's redundant, but still seems to be an oversight.
I wasn't handling errors in the lexers. I'd put in the recommended code to abort parsing on the first error, but I didn't realize I had to do something similar for the lexer. Eventually I found How can I make the lexer exit upon first lexical error? which seems overly twisted, but does the trick. I needed this in the query grammar as well.
SuDecimal.toString wasn't behaving like cSuneido. e.g. 1000 would come out as 1e3. BigDecimal.toPlainString works better in this case, giving "1000", but it doesn't switch to exponential notation for big exponents like cSuneido does. cSuneido switches to exponential notation for exponents bigger than 19. It's probably not critical to match this exactly but it should still switch to exponential notation for big exponents.
The parsers were accepting "extra" stuff on the end. You have to explicitly match EOF. You would think that would be the default. Or at least mentioned prominently. Yet EOF isn't even in the Antlr book index, and it's not in the examples.
One of the big issues with converting Suneido's language parser to Antlr is optional semicolons. I figure I'd better tackle this early in case it's a show stopper. Or if not a show stopper, something that will require a specific approach to writing the grammar.
Thankfully JavaScript (ECMAScript) also has optional semicolons so I could look at examples for it.
You can use Antlr's automatic backtracking, as in one example. But this example works by treating newlines as significant and optionally matching them everywhere applicable - a lot of places!
Backtracking is not used in another example. Instead it uses some tricky code to "promote" applicable newlines to be significant.
The first approach seems simpler, albeit more verbose.
One ambiguity is the return statement. "return 123" can be either "return; 123" or "return 123;". Strangely, Antlr doesn't complain about this issue. It takes "return \n 123" as a single statement, whereas cSuneido would take it as "return ; 123".
Of course, Suneido's handling of optional semicolons is not exactly the same as JavaScript. That would be too easy :-) I'm not even sure cSuneido is totally "correct". For example, cSuneido takes "5 \n -4" as "(5 - 4)" because it looks ahead to see if following lines start with a binary operator. But to me it seems like it "looks" more like "5; -4"
In certain contexts, newlines are not significant. For example, if you're inside a parenthesized expression. And "f() g()" should be legal i.e. two statements. But "(f() g()) should be illegal. This seems to be handled by Antlr with backtracking.
I've always felt a little guilty that I wrote some of the tests for cSuneido in stdlib rather than in C++ code. But it was a lot easier to write certain tests that way. Now that I'm writing jSuneido I find there is an advantage to that approach. To keep the C++ tests I have to rewrite them in Java. But the ones that are in stdlib work as is. It's making me think that it would be nice to have a "complete" set of tests for the executable in stdlib, much like Rubinius initiated an Rspec test suite that "defines" Ruby.
Of course, the reason I'm thinking of this now is that I don't have a clear definition of how optional semicolons are supposed to work in Suneido, other than the cSuneido implementation. A set of tests would be nice! The downside of tests in stdlib is that Suneido has to be fairly complete to run them. And my language implementation is barely started right now.
I had to temporarily kluge some string results from my rules in order to test the parsing, but with not too much messing around it seems to be working. There are some subtle parts: "return expr?" is different than "return | return expr". The first seems to be what I want in order to interpret "return expr" as one statement instead of two.
So far so good. It looks like it may not be too hard to get Antlr to do what I want.
The return value from my language parser was wrapped in another class (constant_return). But that's not the case in my query parser - it returns the result value directly. Obviously the grammars are different but I can't see what would cause this difference. I dug around trying to find an answer but I gave up. If I wanted to spend the time on it I could start cutting down the grammars until I found what was doing it by a process of elimination. But it's easier to just extract the value out of the class. [Later: the problem went away when I added more rules.]
I wasn't handling explicit signs on numeric constants properly. I had an optional minus sign on the lexical rule for numbers, but that only worked if there was appropriate whitespace. "a - 5" was ok, but "a-5" got interpreted as "a -5" (no operator) and gave an error.
The fix led to ambiguities between whether "-5" was a numeric constant or an expression with a unary minus operator. I wanted it to be a numeric constant so I had to change my expression rules a bit.
I notice I don't seem to allow a plus sign on numeric constants in cSuneido. It's redundant, but still seems to be an oversight.
I wasn't handling errors in the lexers. I'd put in the recommended code to abort parsing on the first error, but I didn't realize I had to do something similar for the lexer. Eventually I found How can I make the lexer exit upon first lexical error? which seems overly twisted, but does the trick. I needed this in the query grammar as well.
SuDecimal.toString wasn't behaving like cSuneido. e.g. 1000 would come out as 1e3. BigDecimal.toPlainString works better in this case, giving "1000", but it doesn't switch to exponential notation for big exponents like cSuneido does. cSuneido switches to exponential notation for exponents bigger than 19. It's probably not critical to match this exactly but it should still switch to exponential notation for big exponents.
The parsers were accepting "extra" stuff on the end. You have to explicitly match EOF. You would think that would be the default. Or at least mentioned prominently. Yet EOF isn't even in the Antlr book index, and it's not in the examples.
One of the big issues with converting Suneido's language parser to Antlr is optional semicolons. I figure I'd better tackle this early in case it's a show stopper. Or if not a show stopper, something that will require a specific approach to writing the grammar.
Thankfully JavaScript (ECMAScript) also has optional semicolons so I could look at examples for it.
You can use Antlr's automatic backtracking, as in one example. But this example works by treating newlines as significant and optionally matching them everywhere applicable - a lot of places!
Backtracking is not used in another example. Instead it uses some tricky code to "promote" applicable newlines to be significant.
The first approach seems simpler, albeit more verbose.
One ambiguity is the return statement. "return 123" can be either "return; 123" or "return 123;". Strangely, Antlr doesn't complain about this issue. It takes "return \n 123" as a single statement, whereas cSuneido would take it as "return ; 123".
Of course, Suneido's handling of optional semicolons is not exactly the same as JavaScript. That would be too easy :-) I'm not even sure cSuneido is totally "correct". For example, cSuneido takes "5 \n -4" as "(5 - 4)" because it looks ahead to see if following lines start with a binary operator. But to me it seems like it "looks" more like "5; -4"
In certain contexts, newlines are not significant. For example, if you're inside a parenthesized expression. And "f() g()" should be legal i.e. two statements. But "(f() g()) should be illegal. This seems to be handled by Antlr with backtracking.
I've always felt a little guilty that I wrote some of the tests for cSuneido in stdlib rather than in C++ code. But it was a lot easier to write certain tests that way. Now that I'm writing jSuneido I find there is an advantage to that approach. To keep the C++ tests I have to rewrite them in Java. But the ones that are in stdlib work as is. It's making me think that it would be nice to have a "complete" set of tests for the executable in stdlib, much like Rubinius initiated an Rspec test suite that "defines" Ruby.
Of course, the reason I'm thinking of this now is that I don't have a clear definition of how optional semicolons are supposed to work in Suneido, other than the cSuneido implementation. A set of tests would be nice! The downside of tests in stdlib is that Suneido has to be fairly complete to run them. And my language implementation is barely started right now.
I had to temporarily kluge some string results from my rules in order to test the parsing, but with not too much messing around it seems to be working. There are some subtle parts: "return expr?" is different than "return | return expr". The first seems to be what I want in order to interpret "return expr" as one statement instead of two.
So far so good. It looks like it may not be too hard to get Antlr to do what I want.
Monday, February 09, 2009
Drag and Drop Files into Gmail with Firefox
A post by Tim Bray led me to a post by Mrgan whose main complaint is that you can't drag and drop files onto Gmail attachments.
I agree this is a shortcoming. But if you use Firefox you can fix it with the dragdropupload add-on.
And the advantage is that this add-on works with any web site/app, not just Gmail. Whereas, if one desktop app implemented drag and drop, that wouldn't help any other desktop app.
Mrgan's other main comment is that off-line already works with desktop mail apps. That's true, but I still think Gmail's new offline mode is better - you can use it on multiple machines, and it has a flaky connection mode. And unlike POP (IMAP is better), it keeps your email on the server so you don't have to worry about backing it up.
Even Tim Bray seems to think that email is better as a desktop app. Maybe that's because he uses a single machine (a laptop) for work, home, and travelling. For me, using multiple computers (and operating systems), I think Gmail is a lot better. I think I'd prefer it even if I used a single computer, but the advantage might not be as clear.
And, unlike Tim, I do think that Gmail's "conversations" are a big improvement. They take a little getting used to, so anyone who doesn't use Gmail won't necessarily see the advantage. But I find I really miss Gmail's conversations when I use other email apps.
Another thing I think Gmail should be applauded for is that it doesn't add any "junk" to my messages. I think it's pretty ugly when I get professional business communications with Microsoft or Yahoo ads (spam!) on the end of their emails.
I agree this is a shortcoming. But if you use Firefox you can fix it with the dragdropupload add-on.
And the advantage is that this add-on works with any web site/app, not just Gmail. Whereas, if one desktop app implemented drag and drop, that wouldn't help any other desktop app.
Mrgan's other main comment is that off-line already works with desktop mail apps. That's true, but I still think Gmail's new offline mode is better - you can use it on multiple machines, and it has a flaky connection mode. And unlike POP (IMAP is better), it keeps your email on the server so you don't have to worry about backing it up.
Even Tim Bray seems to think that email is better as a desktop app. Maybe that's because he uses a single machine (a laptop) for work, home, and travelling. For me, using multiple computers (and operating systems), I think Gmail is a lot better. I think I'd prefer it even if I used a single computer, but the advantage might not be as clear.
And, unlike Tim, I do think that Gmail's "conversations" are a big improvement. They take a little getting used to, so anyone who doesn't use Gmail won't necessarily see the advantage. But I find I really miss Gmail's conversations when I use other email apps.
Another thing I think Gmail should be applauded for is that it doesn't add any "junk" to my messages. I think it's pretty ugly when I get professional business communications with Microsoft or Yahoo ads (spam!) on the end of their emails.
Saturday, February 07, 2009
Forking Ruby
Dave Thomas gives an interesting keynote at RubyConf on why it might make sense to fork Ruby.
It's interesting how the new Ruby syntax for hashes is much closer to Suneido's, as is Dave's proposed syntax for blocks/closures.
It's interesting how the new Ruby syntax for hashes is much closer to Suneido's, as is Dave's proposed syntax for blocks/closures.
Subscribe to:
Posts (Atom)