Wednesday, September 2, 2009
Refactoring Immediately
(See my original article on the Hebrew Conjugator project.)
Whenever I had formulated a way to output the correct finite verb form for a given tense/person/gender combination and after implementing exceptions, I found that the formula could be simplified again. So I had to do that in order to avoid that newly discovered exceptions during testing would not make the code ridiculous complicated.
I also noticed that object-orientation is ideal for this, so I implemented another set of subclasses for conjugations of a certain type and for certain exceptions.
The program now has a class "Conjugation" which implements a few helper methods and four conjugation methods:
function GetInfinitive(root: String): String;
function GetPresentTense(root: String; person: Int32; male: Boolean): String;
function GetPastTense(root: String; person: Int32; male: Boolean): String;
function GetFutureTense(root: String; person: Int32; male: Boolean): String;
Each method simply returns an empty string for all input values. This ensures two things. First, any unsupported conjugation can still be called (see note 1) and will not crash the program or return false results. Second, any non-implemented features in a supported conjugation will not return false results either (see note 2).
Note 1: If a subclass of Conjugation does not implement an override for a method, the original method will be called and, in this case, return an empty string.
Note 2: Subclasses of specific conjugations that do not implement an override for a method, will call the specific conjugation's method and return a false result. Hence all subclasses of subclasses of Conjugation will have to implement override methods if the original override method would return false results.
The program then defines subclasses of Conjugation representing specific conjugations (Hebrew: binyanim = buildings), for example ConjugationPa3al.
The subclasses define their own override versions of the four methods.
Orginially, the subclasses also defined new methods for exceptions (like GetPresentTenseHollow for hollow roots), but I found that the code is easier to read if each method follows the same structure in name and content. Hence were born subclasses of the subclasses, including ConjugationPa3alHollow (for hollow roots, like "gar" = "he dwelled" or "ba" = "he came"; "pa3al" is one of the conjugations in Hebrew)and ConjugationPa3alHe (for roots ending on He, like "qanah" = "he bought" and "panah" = "he turned").
Exceptions that only affect the infinitive are handled by the original subclass.
For the infinitive of root ending on He, I found a particularly clever (in my opinion) solution. ConjugationPi3el actually returns the correct infinitive for both roots ending on He ("qanah" -> "liqnoth") and normal roots ("katav" -> "likhtov") and ConjugationPi3elHe does not implement an override for the GetInfinitive() method. This constitutes a combination of not rewriting existing code but still keeping different things in different modules, logically.
Current class tree:
- Conjugator (GUI program uses Conjugation.dll)
- HebrewWord (used by Conjugator)
- Conjugation (all used by HebrewWord)
-- ConjationPa3al
--- ConjugationPa3alHollow
-- ConjugationPi3el
--- ConjugationPi3elHe
Other classes are not implemented yet.
Each subclass of Conjugation and their descendants contain implementations of GetInfinitive(), GetPresentTense(), GetPastTense(), and GetFutureTense(), if needed and nothing else.
Monday, August 31, 2009
Hebrew Conjugator - a Delphi Prism Project
For the past few weeks, I have been working on a Delphi Prism project, a Hebrew verb conjugation program. It is meant to handle regular as well as irregular verbs.
The program consists of two parts, a conjugation library HebrewConjugation.dll and a front end HebrewConjugator.exe. The library defines a class HebrewWord as well as a class Conjugation, a base class from which the several conjugations (in Hebrew: binyanim = buildings) are derived. So far I have implemented parts of two conjugation classes, ConjugationPa3al and ConjugationPi3el.
The base class also defines as constants all letters of the Alefbet (Hebrew alphabet), helper methods to access the characters (for example to modify final letters when appropriate), and empty conjugation methods that simply return empty strings.
public
function Alefbet(i: Int32): String;
function Sofit(s: String): String;
function GetInfinitive(root: String): String; virtual;
function GetPresentTense(root: String; person: Integer; male: Boolean): String; virtual;
function GetPastTense(root: String; person: Integer; male: Boolean): String; virtual;
function GetFutureTense(root: String; person: Integer; male: Boolean): String; virtual;
An object of the type HebrewWord creates an object named conjugation first of the type Conjugation (i.e. the base class) and then of the correct type (e.g. ConjugationPa3al) if such a class already exists. (This means that all conjugations will technically work and not throw exceptions. If a conjugation is not implemented yet, it will return empty strings for any finite verb form requested.)
Catching irregular verbs is done step-by-step, like here:
function ConjugationPa3al.GetPastTense(root: String; person: Int32; male: Boolean): String;
var
r: String;
begin
if (root.Length = 2) then begin // two-letter root
r := GetPresentTenseHollow(root, person, male);
result := r;
exit;
end else begin // three-letter root
The Windows.Forms front end doesn't work on Mac OS X because of, I think, a bug in how Mono handles right-to-left scripts. (Input also doesn't work. I reported the bug to Novell.) So the current version runs on Windows (and perhaps Linux, I haven't tried).
I was planning to build separate front ends for Mac OS X (Monobjc) and Linux (GTK#) anyway, also for WPF and Silverlight (if I find the time).
Plus I plan to add an option to save a root and all finite forms in a (Blackfish SQL) database so that a conjugation once corrected and confirmed can be remembered by the program and accessed when the same root is asked again.
Currently the following items work:
Pa3al: infinitive, present tense (i.e. nouns), past tense (i.e. perfect tense)
Pi3el: infinitive, present tense
Hif3il: nothing
Hitpa3el: nothing
passive conjugations: not supported at all for the moment
Wish me luck!
Sunday, July 12, 2009
Delphi Prism - First Experiment
method ApplicationController.AwakeFromNib;
begin
textInfo.StringValue := 'Hello Prism Cocoa 2 Judgement Day';
end;
AwakeFromNib() is mapped to the Objective-C method awakeFromNib: automatically. I think Prism's NIB reader tool does that. Not all delegate methods are automatic like that. More about that later.
[ObjectiveCMessage('PushOK:')]
method PushOK(aSender: NSObject); partial; empty;
(This is the generated code in designable.pas that maps the method to the action in the NIB file.)
method ApplicationController.PushOK(aSender: NSObject);
var
msgbox: Integer := AppKitFramework.NSRunAlertPanel('Hello Prism Cocoa 2 Judgement Day', 'Somebody pressed the OK button!', 'Really?', nil, nil, nil);
begin
if msgbox = NSPanel.NSAlertDefaultReturn then textInfo.StringValue := 'Somebody pressed the OK button just then.';
end;
[ObjectiveCMessage('windowShouldClose:')]
function WindowShouldClose(window: NSWindow): Boolean; partial;
function ApplicationController.WindowShouldClose(window: NSWindow): Boolean;
var
msgbox: Integer := AppKitFramework.NSRunAlertPanel('Hello Prism Cocoa 2 Judgement Day', 'Really Quit?', 'Yes', 'No', nil, nil);
begin
if msgbox = NSPanel.NSAlertDefaultReturn then Environment.Exit(0) else Result := false;
end;
Monday, July 6, 2009
iPhone debug zombies and finally VMware
A few weeks ago I had an iPhone app running in the simulator that wouldn't quit. I also could not kill it, not using Activity Monitor nor kill or kill -9 or killall.
cyrus:~ ajbrehm$ ps auxc|grep Vocky
ajbrehm 76450 0.0 0.1 153116 5212 ?? UE 12:59pm 0:00.05 Vocky
ajbrehm 76437 0.0 0.1 153116 5212 ?? UE 12:58pm 0:00.05 Vocky
ajbrehm 76350 0.0 0.1 153116 5212 ?? UE 12:57pm 0:00.05 Vocky
ajbrehm 76336 0.0 0.1 153116 5212 ?? UE 12:56pm 0:00.05 Vocky
ajbrehm 76329 0.0 0.1 153116 5212 ?? UE 12:56pm 0:00.05 Vocky
ajbrehm 76276 0.0 0.1 153116 5212 ?? UE 12:56pm 0:00.05 Vocky
ajbrehm 76245 0.0 0.1 153116 5212 ?? UE 12:55pm 0:00.05 Vocky
ajbrehm 76176 0.0 0.1 153116 5212 ?? UE 12:55pm 0:00.05 Vocky
ajbrehm 76159 0.0 0.1 153116 5212 ?? UE 12:54pm 0:00.05 Vocky
ajbrehm 76150 0.0 0.1 155136 9264 ?? UE 12:54pm 0:00.10 Vocky
This happened whenever I ran the application in the simulator. Sometimes Xcode's debugger picked up on it and the simulator wouldn't run the application any more because, as the console sayd, it was already running.
It took me a while to figure out what was going on. But then misfortune struck and taught me the solution.After a while VMware also started behaving oddly and left "vmware" zombies behind in Activity Monitor. One of the VMware instances then blocked the GUI. (It also kept changing the mouse pointer tracking speed for some reason. I have no idea why VMware is allowed to change system preferences and block the GUI.)
Ultimately I had to bring down the GUI (I killed loginwindow and windowserver) and solve the problem.
A few days earlier I had tried switching off Spotlight search because it had started indexing 600 GB of incoming data during a copy process (which took nearly 6 hours because of that, from a Firewire 800 disk). For some reason the only way to stop Spotlight from restarting itself during that copy process was to rename ReportCrash to something else ("ReportCrash2") so that it couldn't be found. That made sense at the time.
The problem was that I had forgotten that I had done that.
Once ReportCrash was usable again, each of the zombies reported its crash and went away, both the iPhone program and the many instances of the VMware interface. This took about 5 minutes. (In the process I learned that the 24" Apple Cinema Display is an EXCELLENT monitor for a text interface! Finally I could see each line without linebreaks.)
After that was done, I restarted windowserver and loginwindow and could log back into the GUI. My VMware VMs had also survived (since I didn't reboot) and VMware worked again and I could connect to all the Windows VMs and the zombie processes had gone.
Turned out it was a ten minute issue once it was clear that it had nothing to do with Xcode or gdb.
I hate ReportCrash.