GeoGad |
Linux's Witness Ministry
|
GeoGad is short for the Geometry Gadget (and long for gg). This is a purely personal software project of Chris X Edwards. The goal is to have a full-featured elegant tool for working with 3d geometry. The Geometry Gadget has similarities to popular engineering CAD systems in the nature of its misson, however, it is a more general tool which can be helpful for more academic geometrical work.
Right now GeoGad is made up of an incomplete set of subsystems. There actually is no special ability to work with geometry at this time.
There are two components that are more or less complete and functional, the rendering engine and the programming language. This page concentrates on the programming language. This is the initial alpha release of this software so that my friends can take a look and play with it. It is not meant to do anything terrifically useful yet beyond demonstrate the important concepts.
When I sat down to think about the capabilities I wanted to see in a geometry modeller, realized that I needed a very flexible syntax to cover a wide range of applications. Ultimately I realized that I really needed a rather complete programming language. With this in mind, I designed one.
This language has many influences. My main influence was my HP48 calculator. If you've ever enjoyed using an HP calculator, then you'll be a good candidate for using this language. It turns out that my language is based heavily on Forth. I didn't know this until it was completed, but the HP48 language is obviously based on Forth too (and Forth seems inspired by HP calculators). Another influence was the PostScript language. Although PostScript takes a special mentality to have fun with, it is a powerful language. The idea of my language was to have features similar to PostScript but with an emphasis on being used interactively by humans (as opposed to printer drivers). Another key difference between PostScript and GeoGad's language is that PostScript has some built-in 2-d features. GeoGad has no intrinsic geometry features yet, but when/if it does, they will be oriented to 3-d capabilities.
The language is implemented in Python so it should run anywhere a normal Python can be found. (I use 2.3.5 right now, so anything higher should work).
It can be downloaded here. The entire lanuguage is about 34kb. Not too bad.
=== The Geometry Gadget === GeoGad Interpreter - Version alpha Chris X Edwards - www.xed.ch Copyright 2005 - GNU Public License gg-->This shows the prompt.
GeoGad has 4 basic object types, value, literal, symbol, and list. This minimal type set makes many things complicated, however, learning how the language works is not one of those things.
If you type:
gg--> ''Sphere:'' 1.33333 pi 2.8794You should see this:
/-------------------- | (4) TXT:Sphere: | (3) VAL:1.33333 | (2) VAL:3.14159265359 | (1) VAL:2.8794 \--------------------
What happened there was that you just entered 4 different objects, a literal, a value, a symbol and another value. The objects are separated by spaces (though, of course it's not that simple for text literals which can naturally contain spaces). You could have typed enter after each object. It doesn't really matter how many objects you put on a single line before you enter them.
The first two objects' types are simple enough; you entered them and they appeared. But the symbol didn't stay a symbol. It got converted into it's value. When a symbol is entered, it is looked up to see if it represents another object. If so, that object is the result. I chose pi because it is a built-in constant. If the symbol is undefined, you'll get a message like this:
gg--> iwishiwasdefined Warning: iwishiwasdefined is not defined.
So you can now enter objects and they appear. Where do they appear? What is happening to those objects? GeoGad uses a stack system (like Lisp, Forth and PostScript). When you enter an object, it goes on the stack at level one. When you enter another object, the previous one you entered goes to level two and the new object is at level one. This is a first in, last out (FILO) system.
What can one do with items in a stack? Quite a lot really! For example, if you just press enter after entering the objects shown above, you get a duplication operation. Just pressing enter:
/-------------------- | (5) TXT:Sphere: | (4) VAL:1.33333 | (3) VAL:3.14159265359 | (2) VAL:2.8794 | (1) VAL:2.8794 \--------------------A blank line of input is the same as typing the command "dup". Try typing the dup command which should produce this:
/-------------------- | (6) TXT:Sphere: | (5) VAL:1.33333 | (4) VAL:3.14159265359 | (3) VAL:2.8794 | (2) VAL:2.8794 | (1) VAL:2.8794 \--------------------
Operations use the stack for data. The "dup" function used the stack to figure out what to make a copy of. Another example of a function that uses the stack for data is the "*". This is simple multiplcation. Enter this:
gg--> *and see this:
/-------------------- | (5) TXT:Sphere | (4) VAL:1.33333 | (3) VAL:3.14159265359 | (2) VAL:2.8794 | (1) VAL:8.29094436 \--------------------8.29094436 is 2.8794 squared.
Since functions operate on the stack, you must develop a sense of trust that the stack will be waiting in the same state that you left it. To see how this works, try this:
gg--> * * *which produces:
/-------------------- | (2) TXT:Sphere | (1) VAL:99.9985089751 \--------------------This did 3 multiply operations taking 2 items off the stack, multiplying them and returning the product each time. You can see that if 2 objects come off and one goes on, then one object is lost for every execution and this is what happened. By the way, this example shows that a sphere with a radius of 2.8794 units has a volume of about 100 cubic units.
The clear function clears all objects from the stack. Do that and get an empty stack. Let's say that for some reason you want to know what diameter tubing is required to fit over a 10mm hexagonal bolt head. The formula would be something like 10mm/cos(30). What if you wanted to calculate that? It turns out to be pretty easy:
gg--> 10 30 cos / /-------------------- | (1) VAL:11.5470053838 \--------------------First the 10 goes on the stack. Then it's bumped to level 2 by the 30. Then the cosine function is called which pulls the 30 off and replaces it again with the cosine of 30. Then the division function is called pulling both values off and returning the quotient.
Earlier we saw how pi converted to the value of the number pi. Symbols play an important part in the language and all of the other commands like clear, dup, and cos are also symbols. When those symbols get resolved, they cause more complex behavior than simply returning data to the stack. Some symbols as we have seen are built-in (you can redefine them, but that's generally a bad idea). That's a handy starting point, but the user can also create symbols. Here's an example:
gg--> 5 |feet sto 10 |inches sto ** Empty Stack ** gg--> feet 12 * inches + /-------------------- | (1) VAL:70.0 \--------------------Here two symbols are created, one called feet and one called inches. The second line of input shows those symbols being used to calculate the total of the two in inches. The mechanisms that are important to creating symbols are the sto function and the "|". The sto function simply stores the value of level 2 into the symbol of level one. That's simple enough, but what is the | doing? The | (I call it a pipe) is a special kind of symbol. When a | is entered it is executed and the results of its execution are to put the symbol of | on the stack. That's an odd thing to do. Why is this so? Every time a symbol other than the | (which is simply special) is submitted as input, a check is made to see what is on level one. If a | is waiting on level one, then the symbol does not get resolved, but gets placed on the stack as a symbol object.
The purpose of this is to allow symbols to be worked with without them spontaneously decaying into their values. Since all objects that get sent to the stack get evaluated right away, it would be impossible to, for example, store the number 5 into the symbol X because the instant that X is sent, it tries to convert itself into its underlying value which would not have been defined. So the way to think about the pipe is that it protects the symbol from evaluation by the interpreter. You might also notice that the | is one of a few special functions and characters that gets auto padded. If you type "|x", it's the same as typing "| x". A space is just automatically put around all pipes. This allows for neater syntax.
The final object type is the list. Lists are collections of objects that occupy one level on the stack. Lists can contain any other kinds of objects, even lists. Lists are created with square brackets like this:
gg--> [''List Example'' 123 pi] /-------------------- | (1) LST:[TXT:List Example, VAL:123.0, SYM:pi] \--------------------
Some functions produce lists:
gg--> 5 range /-------------------- | (1) LST:[VAL:0.0, VAL:1.0, VAL:2.0, VAL:3.0, VAL:4.0] \-------------------- gg--> |++ map /-------------------- | (1) LST:[VAL:1.0, VAL:2.0, VAL:3.0, VAL:4.0, VAL:5.0] \--------------------
The interesting thing about lists is that they have two personalities. Lists can be awake and they can be inert. The lists shown so far have been inert. When they are placed on the stack, they just sit on the stack waiting for subsequent operations. A live list, however, behaves differently when it is submitted as input or otherwise evaluated. Instead of sitting there inert, each component of a live list from first to last is sequentially submitted as input for its own evaluation. This destroys the list which does not then appear on the stack as a list. In the next example, the list is made awake with the "wake" command and then it is evaluated explicitly with the "!" function.
gg--> wake /-------------------- | (1) LST:[VAL:1.0, VAL:2.0, VAL:3.0, VAL:4.0, VAL:5.0]<!> \-------------------- gg--> ! /-------------------- | (5) VAL:1.0 | (4) VAL:2.0 | (3) VAL:3.0 | (2) VAL:4.0 | (1) VAL:5.0 \--------------------
Notice after the wake function there is a <!> appended to the object to show its aliveness. When that object is evaluated, each of its components are sent to the evaluator as if they were each typed in separately. There is another way to use live lists. They can be made awake without the wake function by using this syntax:
gg--> [''This list'' ''is too volatile''::!] /-------------------- | (2) TXT:This list | (1) TXT:is too volatile \--------------------As you can see, the list didn't survive. It immediately decayed into it's components. This behavior is similar to how symbols instantly decay into their values. In fact, the same blocking mechanism of the pipe can be used to enter a live list:
gg--> |[''This list'' ''is volatile''::!] /-------------------- | (1) LST:[TXT:This list, TXT:is volatile]<!> \--------------------
gg--> |[|r sto 4 3 / pi * r 3 pow *::!] |svol sto ** Empty Stack ** gg--> 2.8794 svol /-------------------- | (1) VAL:99.998758972 \--------------------Here a new function, svol, is created which takes a radius off the stack and returns a volume of a sphere. Inside the live list a variable is used for the radius (though this wasn't strictly necessary).
It is a good question to wonder if that variable that is used in this little function is global or local to just the evaluation of the list. It turns out that the default is to work with globals. You can test this:
gg--> r /-------------------- | (1) VAL:2.8794 \--------------------
However it is possible to create local variables by declaring the variables in the metainformation area of the list object:
gg->> |[|r2 sto 4 3 / pi * r2 3 pow * ::r2 !] |svol2 sto ** Empty Stack ** gg->> 10 svol2 /-------------------- | (1) VAL:4188.79020479 \-------------------- gg->> r2 Warning: r2 is not defined. /-------------------- | (1) VAL:4188.79020479 \--------------------
The order of the variables and the ! are not important in this case. If you include a symbol in this section, then it will be treated as a local symbol if it appears in the list.
This technique of using local variables has a simplicity and it doesn't pollute the namespace. If simplicity isn't your style, you could always dispense with variables completely by doing something like this:
gg->> |[4 3 / pi * swap 3 pow *::!] |svol3 sto ** Empty Stack ** gg->> 10 svol3 /-------------------- | (1) VAL:4188.79020479 \--------------------Well, simplicity is a personal thing. I happen to think this is the most simple way of doing this. You might have a different opinion. There are many correct answers.
Lists are objects that can either be oriented to static data or sequences of operations to perform. The reason that this isn't made explicitly into two different object types is that code that gets executed is also data. This is why the list is a single object type.
When a list is specified, the :: separates the actual list content objects from the list metadata. As previously discussed, executable lists contain local variable declaration (if any) and the execution marker (!) in this metadata. In a static list, the metadata for the list is a collection of simple object names. This feature works like this:
gg--> [4 6 4::vertices edges faces] |t gg--> [6 12 8::vertices edges faces] |o gg--> [12 30 20::vertices edges faces] |i gg--> [20 30 12::vertices edges faces] |d gg--> [8 12 6::vertices edges faces] |c /-------------------- | (10) LST:[VAL:4.0, VAL:6.0, VAL:4.0]<vertices edges faces> | (9) SYM:t | (8) LST:[VAL:6.0, VAL:12.0, VAL:8.0]<vertices edges faces> | (7) SYM:o | (6) LST:[VAL:12.0, VAL:30.0, VAL:20.0]<vertices edges faces> | (5) SYM:i | (4) LST:[VAL:20.0, VAL:30.0, VAL:12.0]<vertices edges faces> | (3) SYM:d | (2) LST:[VAL:8.0, VAL:12.0, VAL:6.0]<vertices edges faces> | (1) SYM:c \-------------------- gg--> |sto 5 repeat ** Empty Stack ** gg--> t.edges o.vertices c.faces /-------------------- | (3) VAL:6.0 | (2) VAL:6.0 | (1) VAL:6.0 \-------------------- gg--> [c d i o t::cube dodec icosa octa tetra] |platonic sto ** Empty Stack ** gg--> platonic.octa.edges platonic.dodec.faces platonic.cube.edges /-------------------- | (6) VAL:6.0 | (5) VAL:6.0 | (4) VAL:6.0 | (3) VAL:12.0 | (2) VAL:12.0 | (1) VAL:12.0 \--------------------
To clear up any confusion about list metainformation, here's a review. The list contains objects (or is empty). If a list is specified with a "::" somewhere in it, then the true objects of that list all preceed the "::". The "::" itself is not an object, but rather a syntax element just to enter data. The elements that are in the metainformation, everything between the "::" and a closing "]", are not objects. They are just names that refer to objects. In the case of the static list, the names refer to objects based on their corresponding position, i.e. the first metainformation element names the first list object. Live lists contain a "!" somewhere in the metainformation; it does not matter where. Live lists can also specify names of symbols to treat as local to the execution of this list. These can be in any order.
Though it might seem like the system described so far is little more than a fancy abacus with objects on a stack instead of beads on a wire, GeoGad is actually a lot more clever than that. The simple organization of its data structures allows for functions with a consistent interface that can produce all of the complex behavior of a normal programming language.
The most fundamental aspect of programming language logic is a branching mechanism. Most languages have some kind of "if" statement and GeoGad is no exception:
gg--> clear 10e10 sqrt floor 2 mod ''Odd'' if /-------------------- | (1) TXT:Odd \-------------------- gg--> clear 10e9 sqrt floor 2 mod ''Odd'' if ** Empty Stack ** gg--> clear 10e9 sqrt floor 2 mod ''Odd'' ''Even'' ifelse /-------------------- | (1) TXT:Even \--------------------Here is an example of the if and ifelse commands in action. Note that the if command only takes 2 objects from the stack. All of the activity up to the if command is doing whatever is necessary to prepare the stack to contain the right things to base the decision on. You can't look at the first example and imagine that the if is going to do something with the mod function and the text object "Odd". When this sequence of objects is actually evaluated, the mod will do something (find the modulus, or remainder when divided by 2) and will be long gone by the time it is if's turn to be evaluated. What will be there at that time needs to be carefully thought out. Fortunately this kind of system makes it easy to take interactive playing and just formalize it into little sequences of predictable code.
Looping logic has a similar construction to everything else in GeoGad. Here two examples of a classic loop. They both do exactly the same thing in slightly different ways.
gg--> clear 2003 |[dup 2006 le::!] |[dup ++::!] while /-------------------- | (5) VAL:2003.0 | (4) VAL:2004.0 | (3) VAL:2005.0 | (2) VAL:2006.0 | (1) VAL:2007.0 \-------------------- gg--> clear 2003 |[dup dup 2006 lt::!] |++ while ++ /-------------------- | (5) VAL:2003.0 | (4) VAL:2004.0 | (3) VAL:2005.0 | (2) VAL:2006.0 | (1) VAL:2007.0 \--------------------Notice how these loops are pretty good approximations of the classic C style loop (whose syntax I find even more bizarre than this). This C for loop:
for (i=20; i<50; i+=6) {leave_num_on_stack();}:...can be translated like this:
gg--> clear 20 |i sto |[i 50 <::!] |[i i 6 + |i sto::!] while /-------------------- | (5) VAL:20.0 | (4) VAL:26.0 | (3) VAL:32.0 | (2) VAL:38.0 | (1) VAL:44.0 \--------------------
The GeoGad system is designed to both be easy to learn and understand,
yet rich in features and abilities. The concept is that the
fundamental mechanics of the language are quite compact and the
diverse usefulness comes from diverse functions. Consequently, there
are dozens of functions with new ones emerging at an alarming rate.
The best strategy for making the system useful is to learn the
fundamentals shown here and then learn to rely on the built-in help. Each
function is should be augmented with an explanation of it and a
stack diagram showing the way it handles input and output. To access
this information, use the "help" command. Once you understand the
basic mechanics of the language, the help command should keep you
organized. Here is it's output at this time:
gg--> help -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= Active general topics: info, flow, help, system, logic Active commands: !, #, *, +, ++, -, --, -> /, 2deg, 2list, 2rad, <, <=, =, ==, >, >=, ?, ZZ, abs, acos, add, addvec, allvec, and, asin, atan, avg, ceil, clear, cos, dd, depth, dgg, div, drop, drop2, drop3, dropn, dup, dup2, dup3, dupn, e_, eq, eval, exit, exp, filter, floor, for, foreach, ge, getvec, ggdG, gt, help, helpall, idiv, if, ifelse, inert, inv, lastvec, le, len, log, log10, logbase, lt, map, mod, mul, nand, ne, neg, nor, not, or, over, pi, pickn, pow, put, quit, range, redo, repeat, rot, sin, sqrt, sto, sub, sum, swap, tan, undo, undocheck, undon, unsto, vector, version, wake, while, xnor, xor, yank, yy For help on a particular command use the "?" command: ... ''helpall'' ? ==> ... For help on a general topic use a list with the "?" command: ... [help] ? ==> ... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=The first part shows the list of general help categories. The next section lists all the available commands. The last few lines remind you how to get more detailed information. Basically you enter the word you want help with as a text object or a symbol object in a list. Then the "?" command gets information about the command or topic respectively.
Here's an example of the "?" in action:
gg--> ''rot'' ? ''le'' ? -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= rot ... OB3 OB2 OB1 rot ==> ... OB2 OB1 OB3 aka: 3 yank -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= le OB2 OB1 le ==> <1|O> True if level 2 is 'less than or equal' to level 1. -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=The "helpall" function will print the help information for all functions. That can be nice to look through.
What happens if you do something like this?
gg--> [1 2 3::a b c d] |awkward sto awkward.dAt this time you get a gruesome crash of the entire interpreter. Don't do this! (I will fix this.)
Another thing that is not quite perfected yet is the ability to store using the named objects. Eventually I will add the capability to do this:
gg--> [1 2 3::a b c] |ok sto 3.0001 |ok.c stoThis is a slightly complex thing to add, but it will be done.
Right now there is no great way to get at list components besides just recalling their values by their name. There is not even a great way to modify the namespaces after the list is established. Eventually I want to have a concept of templates where a list can be defined like this:
gg--> [::x y z] |pt sto [1 2 3] pt embellish /-------------------- | (1) LST:[VAL:1, VAL:2, VAL:3]<x y z> \--------------------Also, I'd like a Python style slice mechanism (which shouldn't be too hard since Python is quite up to the task).
gg--> [0 1 2 3 4 5 6] 2 4 slice /-------------------- | (1) LST:[VAL:2, VAL:3] \--------------------Actually, I'm not sure I really love how Python numbers slice elements.
Return Linux Stuff |
This page was created with only free,
open-source, publicly licensed software. This page was designed to be viewed with any browser on any system. |
Chris X. Edwards ~ February 2006 |