Umm, since when is anything in the dragon book considered "revolutionary"? The DFA->NFA conversion algorithm has been (a) known for a loooong time and (b) used for compiling regexes the whole time. How to you think lex works?
But, they are exponential in time and space in the worst case (doesn't happen often), but once compiled, the matching time is linear in the size of the candidate string. But again, those big tables are neither cache, pipeline, nor paging friendly (not an issue when the DFA algorithm was discovered), so a matcher that minimizes backtracking can often work well.
But writing such switch statement (that handles strings) is trivial in lisp. It would be useless, since lisp already does this, but it would still be trivial.
I recently wrote a macro for use in a VM I was playing with, that is used for defining opcodes,
and is used something like this:
The definstruction macro takes the opcode number, opcode name, a stack picture, and the body of the function. It then expands into code for a function that implements the opcode. In the case of addi, this function verifies that there are at least two items on the stack, and that they are both integers, throwing appropriate exceptions for each error case. It then generates code to pop these items off the stack into local variables and also declares the output variable, making sure not to double-declare if some of the same variables are used on both the input and output. It then generates the body of the function wrapped up in an exception handler that restores the stack to its initial state before calling the exception hook if it's a restartable exception, or just calls the exception hook directly if it's a resumable exception (yes, Lisp has resumable exceptions so you can clean up and keep going). The macro then generates code to push the result back on the stack, checking for overflow if needed, and (optionally, for debugging purposes) verifying that the result was indeed of the correct type.
Finally, it generates code to add the opcode to the jump-table that the vm interpreter uses, and also to the tables that the disassembler uses.
For something like addi maybe 30 lines of code are generated. For something like swap, it only generates about 4, because the macro is intelligent enough to realize that with no body and no typechecking it doesn't need the exception handlers etc.
This one macro is under 100 lines of lisp code, and will save 5000 to 10000 lines boring, repetitious, bug-prone code!
On my home machine, for example, I type 'halt machine', or utilize it's command completion and just type 'ham' which exits the operating system and returns me to a bootstrap command processor from which I can launch myself into another world with 'boot ', or shutdown the system completely by typing 'shutdown'. If I change my mind, I can get back where I was by typing 'restart'.
What is this amazingly intuitive system? Genera 8.3, on a 1991 Symbolics XL1200 (an old AI workstation).
The original article was correct in this respect -- modern systems may have lots of pretty bells and whistles, but they have really gotten untracked from usability and reliability standpoints.
..is that bloody-minded insistence on character stream interfaces between programs. Sure it's more flexible than records, but it's an awfully lowlevel way to exchange data.
Symbolics' Genera operating system used a system called Dynamic Windows, that nicely integrated the command processor and GUI. The command processor was both simpler and more powerful than Unix shells. Simpler, because scripting was provided by the Lisp interpreter. More powerful, because commands didn't just return a result code -- they returned entire objects (or multiple objects!). The presentation manager was responsible for formatting those objects in an appropriate manner. The Show Directory command returned a list of pathname objects, which would be shown as a list. The nice thing is that the GUI knew what they were, and they remained active objects: You could right-mouse-click on a pathname and get a menu of appropriate actions (Edit, Show, Delete, etc).
Similarly, the commands themselves were integrated into the GUI. For example, the Command Processor knew that the Show File command took a pathname as argument. If you typed Edit File (without hitting return) all the pathname objects in the window would become active, and you could click on one to send it to the Edit File command. Or you could hit Ctrl-? to pop up a list of possibilities.
If a command needed some more complicated interaction, it could send a dialog object to the command processor, who would append it to the window, allow the user to fill it out, then send the results back to the command.
Full GUI apps were easy as well, even a GUI application had the same basic model, it just needed to turn off scrolling, turn off command echo, change the menu to an application-specific one, and send Dynamic Windows whatever presentation objects it needed to get cranked up. Menu entries, mouse clicks and other gestures, and accelerator keys were translated into commands and sent to the application's Command Processor.
There was a lot more to the whole thing, of course, and this short description doesn't begin to touch richness of the system, but the closest I can imagine a stream-based system getting is via XML, which is again an awfully low-level way of attacking the problem.
Followed by a long hard dance in bed. Followed by Mexican food and big-*ssed margaritas from the local Tex-Mex restaurant.
As my wife told me shortly before I proposed, "Nothing says I love you like going shooting with your honey on Valentine's day."
Umm, since when is anything in the dragon book considered "revolutionary"? The DFA->NFA conversion algorithm has been (a) known for a loooong time and (b) used for compiling regexes the whole time. How to you think lex works?
But, they are exponential in time and space in the worst case (doesn't happen often), but once compiled, the matching time is linear in the size of the candidate string. But again, those big tables are neither cache, pipeline, nor paging friendly (not an issue when the DFA algorithm was discovered), so a matcher that minimizes backtracking can often work well.
But writing such switch statement (that handles strings) is trivial in lisp. It would be useless, since lisp already does this, but it would still be trivial.
I recently wrote a macro for use in a VM I was playing with, that is used for defining opcodes,
and is used something like this:
(definstruction 32 addi ((n1 integer) (n2 integer) -- (result integer))
(setq result (+ n1 n2)))
(definstruction 10 swap (a b -- b a)
)
The definstruction macro takes the opcode number, opcode name, a stack picture, and the body of the function. It then expands into code for a function that implements the opcode. In the case of addi, this function verifies that there are at least two items on the stack, and that they are both integers, throwing appropriate exceptions for each error case. It then generates code to pop these items off the stack into local variables and also declares the output variable, making sure not to double-declare if some of the same variables are used on both the input and output. It then generates the body of the function wrapped up in an exception handler that restores the stack to its initial state before calling the exception hook if it's a restartable exception, or just calls the exception hook directly if it's a resumable exception (yes, Lisp has resumable exceptions so you can clean up and keep going). The macro then generates code to push the result back on the stack, checking for overflow if needed, and (optionally, for debugging purposes) verifying that the result was indeed of the correct type.
Finally, it generates code to add the opcode to the jump-table that the vm interpreter uses, and also to the tables that the disassembler uses.
For something like addi maybe 30 lines of code are generated. For something like swap, it only generates about 4, because the macro is intelligent enough to realize that with no body and no typechecking it doesn't need the exception handlers etc.
This one macro is under 100 lines of lisp code, and will save 5000 to 10000 lines boring, repetitious, bug-prone code!
On my home machine, for example, I type 'halt machine', or utilize it's command completion and just type 'ham' which exits the operating system and returns me to a bootstrap command processor from which I can launch myself into another world with 'boot ', or shutdown the system completely by typing 'shutdown'. If I change my mind, I can get back where I was by typing 'restart'.
What is this amazingly intuitive system? Genera 8.3, on a 1991 Symbolics XL1200 (an old AI workstation).
The original article was correct in this respect -- modern systems may have lots of pretty bells and whistles, but they have really gotten untracked from usability and reliability standpoints.
Symbolics' Genera operating system used a system called Dynamic Windows, that nicely integrated the command processor and GUI. The command processor was both simpler and more powerful than Unix shells. Simpler, because scripting was provided by the Lisp interpreter. More powerful, because commands didn't just return a result code -- they returned entire objects (or multiple objects!). The presentation manager was responsible for formatting those objects in an appropriate manner. The Show Directory command returned a list of pathname objects, which would be shown as a list. The nice thing is that the GUI knew what they were, and they remained active objects: You could right-mouse-click on a pathname and get a menu of appropriate actions (Edit, Show, Delete, etc).
Similarly, the commands themselves were integrated into the GUI. For example, the Command Processor knew that the Show File command took a pathname as argument. If you typed Edit File (without hitting return) all the pathname objects in the window would become active, and you could click on one to send it to the Edit File command. Or you could hit Ctrl-? to pop up a list of possibilities.
If a command needed some more complicated interaction, it could send a dialog object to the command processor, who would append it to the window, allow the user to fill it out, then send the results back to the command.
Full GUI apps were easy as well, even a GUI application had the same basic model, it just needed to turn off scrolling, turn off command echo, change the menu to an application-specific one, and send Dynamic Windows whatever presentation objects it needed to get cranked up. Menu entries, mouse clicks and other gestures, and accelerator keys were translated into commands and sent to the application's Command Processor.
There was a lot more to the whole thing, of course, and this short description doesn't begin to touch richness of the system, but the closest I can imagine a stream-based system getting is via XML, which is again an awfully low-level way of attacking the problem.