GeoGad - The Geometry Gadget

gglogo1

Contents

Introduction

GeoGad is short for "Geometry Gadget" and it is a programming language I wrote and implemented as part of a geometric modeler. When anyone ever creates a new programming language, there is one dominant question which must be answered: For the love of god, why!?

The purpose of this language is primarily to support user interactive input. Having become completely convinced of the utility and brilliance of Hewlett-Packard’s Reverse Polish Notation calculators for user facing calculation work, I thought that such a system would be ideal for technical geometry modeling. Another influence that proves the utility of this approach with geometry is PostScript. Unfortunately, PostScript is not ideal to work with interactively and it is mired in two dimensions. GeoGad seeks to create an RPN/RPL style language like that found on later HP calculators such as the beloved HP-48 and HP-28, for example, which will be ideal for working with accurate technical geometry.

The language is currently implemented in Python and pretty much anything Python can do can be quite easily implemented in this language as a function. It can be thought of as a way to turn Python into an interactive HP Reverse Polish Lisp kind of experience. I have been using it as a very powerful calculator pretty much every day now since 2005.

Download

The source code is available for you to take a look at and play with. This is mostly to easily have gg available to me. It is also for my friends and people who would like to see what kind of software I write and like. The URL is simply this.

Unpack this tar file and run the gg executable with Python 3.

Starting from a safe directory (such as /tmp) you can download and run GeoGad with this single line. (Or do it in two steps and review it to your satisfaction first.)

:->[/tmp]$ wget -qO- xed.ch/p/gg/gg.tgz | tar xz ; gg/gg
=== The Geometry Gadget ===
    GeoGad Interpreter - Version alpha-14.5
    Chris X Edwards  -  www.xed.ch
    Copyright 2005-2014 - GNU Public License
gg--> 5 range [10] 5 :* * ''Seems to work!''
      /--------------------
      | (2) LST:[VAL:0.0, VAL:10.0, VAL:20.0, VAL:30.0, VAL:40.0]
      | (1) TXT:Seems to work!
      \--------------------
gg--> exit
Thanks! Bye!

Try entering the (space-separated) items in my test line one at a time to get a feel for what it is doing.

Geometry

In 2014, I finally was able to write a substantial portion of the functionality to actually work with geometry. The way this works is that there is a module called mm which stands for "memory model" and is responsible for keeping track of the geometry while it is in memory and available to the user through the GeoGad interface. This memory model is mainly made of "entities". All entities and geometry are fully three dimensional. Currently there are only two kinds of entities, "lines" and "tri faces". It’s important to note that the lines are visible while the tri faces are not. The only purpose to the tri faces is to provide opaque surfaces used in hidden line removal. If you want a visible tri face, create a tri face and then three lines that use its vertices.

The points that define lines and tri faces come from a list of points that is automatically managed by the mm module. The user never needs to access the points of the memory model directly. The list of points is always automatically normalized to contain the fewest number of points that will suffice to define all entities.

The model shown above was generated entirely with GeoGad.

Any time anything is done to the memory model’s geometry which would create a noticeable change, the entire model is dumped to the to2d program. The to2d program is a unix-style command-line rendering engine that I wrote in Cpp. It takes as its input a list of numbered points followed by records of either two or three fields which are point numbers comprising lines or tri faces respectively. Options to the to2d program control various aspects of the rendering such as camera placement and orientation. GeoGad has functions to control these options from the interpreter. The to2d program is very powerful and can make a full 3d vector model into a completely accurate 2d vector model of the rendered scene. This includes hidden line removal, shallow angle culls, and other features.

The output of to2d is a series of lines in a format compatible with scalable vector graphics (SVG) syntax. This is collected and prepared by GeoGad into an HTML file which is saved in /tmp/gg.html by default. By pointing a web browser to this file, the model can be seen. The HTML file has a refresh feature which will cause it to reload itself so that as the user works with the memory model, the results are continuously displayed in the browser.

This vector image (check the page’s source if you’re interested) was generated with my to2d Unix command line rendering engine. It’s not perfect (I count two errors), but it’s pretty decent with hidden line removals considering that you get 2d geometry, not just a raster image.

Language

The GeoGad language is conceptually very simple. Input objects are sent to an evaluator which produces output objects. These output objects are sent to a FILO stack where they can be accessed by the evaluator in subsequent operations.

RPN - Reverse Lisp

The most powerful programming language is Lisp. If you don’t know Lisp, you don’t know what it means for a programming language to be powerful and elegant. Once you learn Lisp, you will understand what is lacking in most other languages.

— Richard Stallman

If you are familiar with RPN, feel free to skip this section. If you’re not used to PostScript or RPN calculators, here’s a quick demonstration of how simple things typically work in such systems including GeoGad.

In a normal calculator or programming language, if you want to add "3" to "4" you would use syntax like 3+4= or x=3+4. This is very different from GeoGad which is stack based. To accomplish this task, put the 3 on the stack ("3" then "Enter") then put the 4 on the stack, and finally put a + on the stack. The last part, the + is immediately removed and "evaluated". It is a symbol that maps to an addition function. This function starts by pulling two values off the stack, in this case the 3 and 4, and then adding them together. The result of this is then put back on the stack.

For those not already absolutely delighted by this way of doing business, this kind of "reverse" thinking can be very disturbing. Why would anyone want to do this? For one thing, it turns out that this is a very efficient way for computers to manage affairs. However, there are benefits for humans as well. Most of the syntax for traditional languages comes from trying to approximate the look of math notation handed down through the ages. Is this traditional math notation ideal? Nope. Definitely not. To see why let’s look at an example. Imagine typing this into a pocket calculator:

3 + 4 * 2 =

Although programming languages can generally get this right, the calculator would fail (or require some fancy programming). The problem is that the order of operations is not well understood by a cheap calculator. In normal programming, if you wanted to do the addition first, you’d need:

( 3 + 4 ) * 2 =

Compare both of these with their equivalent syntax in GeoGad:

4 2 * 3 +
3 4 + 2 *

Or how about this example from the source code of GeoGad which burned me. This is part of the calculation for finding a point a certain ratio between two points. Considering just one axis, here is what I started with:

x= P1[0]-P0[0] * ratio + P0[0]

That produced comically wrong answers until I hunted down that I was missing parentheses.

x= (P1[0]-P0[0]) * ratio + P0[0]

It looks easy and obvious when it’s pointed out but clearly this kind of mistake can happen all too easily. In this case the spacing I used reflected wishful thinking rather than correct code. The RPN style languages simply do not let you think in ways that can produce errors like this.

There are no sneaky operator precedence rules. Parentheses are never needed. Things are put on the stack and evaluated as they arrive and there is never ambiguity. Notice also that the GeoGad way does not require an = sign. The + operator immediately takes two values off the stack and pushes the sum back on the stack. There’s nothing more to be done.

Once you get used to the idea, it’s quite useful to be able to use the stack as an infinite scratch place to store and manipulate intermediate results. Having a stack is also useful for calculations that may have many outputs (e.g. value of sine between 0 and 30 every 10 degrees) or many inputs (e.g. find the average of these numbers).

4 range |[sin::!] map wake ! -> 0.0 0.017452 0.034899 0.052335
0.0 0.017452 0.034899 0.052335 4 2list avg -> 0.0261715

If you’re still not quite getting the idea, this video very patiently explains RPN.

Transaction Notation

First a quick note about the notation used in GeoGad documentation. If objects A, B, and C are sent to the evaluator in that order, and D is output first, E second, and F third to the stack, then the notation is:

A B C -> D E F

This is a shorthand of the entire interaction which could have transaction notation explicitly for each operation. For example:

1 2 3 -> 1 2 3

This could also be laboriously written:

1 -> 1
2 -> 1 2
3 -> 1 2 3

Also, pre-existing objects on the stack which are not affected can be assumed in the transaction notation:

static_stuff_not_shown A B C -> static_stuff_not_shown D E F

Language Format

GeoGad is not line-based. End of line codes are just like spaces. Spaces are the default interobject delimiter. Any series of objects separated by spaces will be sent to the evaluator in that order. Some special characters always should be separated from other objects and therefore, ironically, you don’t have to include them yourself. The interpreter will add them automatically for you. It is helpful, however to understand that they are needed, but the interpreter is just adding them for you. The special characters that get autospaced are [, ], !, |, #, and ::. So if you want to name a symbol something very odd like odd#s, it will not work. Instead, the interpreter will send three symbols to the evaluator, odd, #, and s, as if you had typed odd # s.

Object Types

To achieve a degree of user simplicity, there are only four types of objects in the entire language.

Short Label Type Example

T

TXT

Literal

''An example! Text can span multiple lines.''

V

VAL

Value

3.141

S

SYM

Symbol

pi drop dup sin X1 *Pretty_Much_Anything*

L

LST

List

[2 Pi mul] [dup *::!] [0 1 2::x y z]

Objects can be dead or live. Dead objects are inert and have no special functionality while live objects can undergo some spontaneous change. TXT and VAL objects are always dead. The evaluator can only copy, delete, or synthesize these objects.

''Text'' -> TXT:Text
1066 -> VAL:1066.0

SYM objects are always live. The practical effect of this is that symbols spontaneously resolve themselves into what they stand for whenever possible. LST objects can be either dead or alive though their default is to be dead. A live list which is evaluated will evaluate each item the list contains in order. Therefore a live list is like a function or macro.

Think of it like this - Literal and value objects are like oak or pine wood: you want the tree to be dead and cut into lumber for it to be useful. Symbol objects are like lemons or avocados: you want the live tree to produce something else (fruit). List objects are like walnut or cherry: sometimes you want the dead tree and sometimes you want what it produces when alive.

Text Literals

It will be helpful to understand the motivation behind the odd style of text input. In engineering work (in the USA), it is very common in geometrical work to have text like this:

(3) 1' 2-1/4" C'BORE 2" DP.

This presents a bunch of common and irritating problems. With GeoGad, this text string is entered like this:

''(3) 1' 2-1/4" C'BORE 2" DP.'' -> TXT:(3) 1' 2-1/4" C'BORE 2" DP.

The main problem with this kind of thing is how then do you specify two single quotes?

separators

This is where the magic character comes in. Normally it is ^.

Here’s an example of this which does typically occur annoyingly when describing the system itself. The text that is typed in at the prompt in this example is crafted to produce text objects whose text is accurate (they themselves are not accurate because of the escaping necessary).

gg--> ''Text is specified with two apostrophe (') characters.''
gg--> ''Typing '^'foo'^' would make a text object "TXT:foo".''
gg--> ''Note that to get that text '^^' was typed where a '^' was desired.''
gg--> ''This can be repeated indefinitely to get '^^^' and so on.''
      /--------------------
      | (4) TXT:Text is specified with two apostrophe (') characters.
      | (3) TXT:Typing ''foo'' would make a text object "TXT:foo".
      | (2) TXT:Note that to get that text '^' was typed where a '' was desired.
      | (1) TXT:This can be repeated indefinitely to get '^^' and so on.
      \--------------------

Generally, this won’t be used very much in practical situations. Basically, any pair of quotes surrounding nothing but magic characters returns the original with one of the magic characters missing.

gg--> '''^^^^^'''
      /--------------------
      | (1) TXT:'^^^^'
      \--------------------

This allows for entering pretty much any arbitrarily odd and messy text so long as it doesn’t use the very rare idea of two apostrophes. Double apostrophes are arguably no more difficult to type than shift-apostrophe (or a double quote). I felt that this was a better system than using single or double quotes (or backticks) with annoying escapes every time you want to say "Don\'t use \"quotes\" ironically."

Note that the current interpreter isn’t very good about doing the right thing when it comes to complicated text problems in conjunction with special auto-spaced characters. Use caution in such cases.

Symbols

Symbols tend to undergo a one-way change into the object they represent. This is the natural behavior:

X1 -> TXT:Value of X1

Symbols can be protected from the evaluator by preparing the stack to contain a protector which is the pipe, "|" which itself is a symbol and a special auto-spaced character.

|X1 -> SYM:X1

This allows symbols to be used as symbols and not their contents. This is essential for defining the contents (among other things). The symbol sto dereferences to a built-in command that assigns the value on level 2 of the stack to the symbol on level 1.

3 |X1 sto X1 -> VAL:3.0
| 3 |X1 sto X1 -> SYM:X1

To force an evaluation, use the evaluator symbol, !.

| 3 |X1 sto X1! -> VAL:3.0
|! -> SYM:!
|!! -> ** Empty Stack **

Symbols continue to recursively dereference in a single operation until it is no longer possible.

10 |X sto |X |Y sto Y -> VAL:10.0

Values

If an object is not explicitly marked as text or a list, then it is a symbol or a value. If it can be made into a value, it is. The situation this might cause problems with is with exponential notation. For example:

|1e1 -> SYM:| VAL:10.0
|1ee1 -> SYM:1ee1

There is no distinction between integers and reals. Save that for fancy inconvenient-to-humans languages. In gg, a value is a numeric value with no strings attached.

Lists

Lists are collections of objects. They can be recursive i.e. they can contain lists. Lists are reducible but durable, i.e. they tend not to be reduced unless explicitly made to do so.

[''Text'' 3 |X1 [3 2]] ->
   LST:[TXT:Text, VAL:3.0, SYM:|, SYM:X1, LST:[VAL:3.0, VAL:2.0]]

The basic way to collect some actions into what is normally a function is to put it in a special list. Lists can hold anything (other lists, symbols, symbols which are commands, values, text). As a default, lists have the property of being inert. An inert list just contains stuff and that is that. A list can however be awoken with the wake command. Here is an example of a normal list doing not much of anything but holding data:

[3 5 +] -> LST:[VAL:3.0, VAL:5.0, SYM:+]

But if you "wake" this list, an attribute is set that allows its contents to be deployed and evaluated on the stack as commands. When a live list is evaluated, the objects it contains are sent to the evaluator as fresh input in order.

[3 5 +] wake -> LST:[VAL:3.0, VAL:5.0, SYM:+]<!>

Note the <!> attached to the list. Lists like this can be activated so that each item it contains will be put on the stack in order and evaluated. To evaluate it use the eval command.

[3 5 +] wake eval -> VAL:8.0

This can be shortened with the special syntax in the name space property of the list. Since the list is not just data if it is to be executed, it needs no naming. Instead of names, using ! will signify that the list is to be an executable list.

[3 5 +::!] -> VAL:8.0

Notice that putting a "waked" list on the stack immediately evaluates it. To protect it, defer evaluation with a |.

|[3 5 +::!] -> LST:[VAL:3.0, VAL:5.0, SYM:+]<!>

The eval command has an alias of ! which allows for this kind of syntax to be somewhat shorter.

|[3 5 +::!] ! -> VAL:8.0

The ability to wake lists into functional code allows for the creation of macros. When a list that has the wake property activated is stored into a symbol with the sto command, it creates a named block of executable code. When the symbol name is put on the stack, the list is evaluated.

Here is a simple (but useful) macro that clears the stack.

|clear |c sto -> ** Empty Stack **
''some'' ''stack'' ''cruft'' c -> ** Empty Stack **

Here’s an example which converts miles into kilometers.

|[12 5280 * 25.4 * 1e6 / *::!] |tokm sto -> ** Empty Stack **
26.2 tokm -> VAL:42.1648128

If you want to free up any name associated with a stored variable, you can use the unsto.

|tokm unsto -> ** Empty Stack **

Named List Items

Sometimes it is useful to be able to refer to a member object of a list by some name. This is like using a hash or dictionary in other languages, but here it is less efficient or clever. The advantage is that the list is still ordered. Also, these namespace symbols are optional. The symbols are put into a name area like this:

[39 25 16::x y z]

There is a one to one ordered correspondence between names and list objects. If there are too many names, the extras represent null values. If there are insufficient names, the extra objects are unnamed.

[39 25 16::x y z w]
[39 25 16::x]

List names are used like this:

[39 25 16::x y z] |P1 sto P1.y -> VAL:25.0

This will be confusing for the interpreter, but it won’t cause a crash and will just return nothing to the stack.

[39 25 16::x y z w] |P1 sto P1.w ->

If the name used isn’t in the list’s namespace, then the whole list comes back.

[39 25 16::x] |P1 sto P1.y ->  LST:[VAL:3.0, VAL:9.0]<x>

The list namespace can also be used to wake lists. This is true because the typical use for lists is either as a container of named data or a function macro, usually not both. However the list object was designed so that it could be easily switch back and forth between the two modes. To create a live list use this syntax:

12 [ 25.4 * ::!] -> VAL:304.8

That’s actually a bit useless. This is more useful:

|[ 25.4 * ::!] |2mm sto 12 2mm -> VAL:304.8

Once a list is already created either on the stack or stored in a variable in memory, it is possible to access and edit the list’s name set. The names command takes a list or SYM of a list from v1 and if that list has a name component, they are extracted into a separate list as TXT objects.

The decorate function takes a list from v2 and a name list from v1 and replaces the v2 list’s name space with the value of v1. The name list must be composed of only TXT or SYM objects. If they are SYM objects, they are not resolved but used as names (the SYMs need not be defined). This should also take a SYM of a list. If that is the case, the output should be nothing but the SYM should contain the updated list. If a LST is supplied as v2, then an updated list is returned. Any existing names are lost.

[1 2 3] [one two three] decorate -> [1 2 3]<one two three>
[7 8 9] |X sto |X [i j k] decorate X.j -> 8

To change the naming of items in a list, names can be used with repl and decorate.

[100 .9784 .8591 .5798::jpy usd franc pound] |money sto
|money money names 3 [''chf'' ''gbp''] repl decorate
money -> [100 .9784 .8591 .5798::jpy usd chf gbp]

Name Scope And Local Variables

When a non-dead list is having its components evaluated, it is sometimes desirable to have access to variables that are local only to the evaluation of the list. The namespace can be used to achieve that effect too. Any names found in a namespace which also contains a ! will be treated as local variables which are active and accessible only for the duration of the execution of the list. Note the position of the ! or any of the names does not matter in this context.

|[|X sto |Y sto Y Y * X X * + sqrt::! X Y] |R sto ->
3 4 R -> VAL:5.0

In order to create functions that take values off the stack and then work with them, there is a local variable loader function . Using it allows the function in the previous example to be written:

|[-> Y Y * X X * + sqrt::!X Y] |R sto ->
3 4 R -> VAL:5.0

Any local variable name that has the same name as something in the global symbol table, will mask the global version.

''Original X'' |X sto 3 4 R X -> VAL:5 TXT:Original X

Lists which are themselves components of a list can access the namespace of the parent list if it is not overridden with a local definition specific to the sublist.

1 |A sto
    [2 |A sto ''2b'' |b sto
        [3 |A sto
            [4 |A sto A b::!A]
        A::!A]
    A::!A]
A -> VAL:4.0 TXT:2b VAL:3.0 VAL:2.0 VAL:1.0

OOP

List namespaces can be used to create a facsimile of object-oriented behavior where data and methods can be encapsulated.

gg--> [3 [dup Repeater.times -- repeat::!]::Times Exec] |Repeater sto
      ** Empty Stack **
gg--> ''Hip hip hooray!'' Repeater.Exec
      /--------------------
      | (3) TXT:Hip hip hooray!
      | (2) TXT:Hip hip hooray!
      | (1) TXT:Hip hip hooray!
      \--------------------
gg--> clear 2 |Repeater.Times sto
      ** Empty Stack **
gg--> ''Two cheers!'' Repeater.Exec
      /--------------------
      | (2) TXT:Two cheers!
      | (1) TXT:Two cheers!
      \--------------------
Note
See the Bugs section to avoid getting burned by the current known problems with this.

List Functions

  • 2list - Takes a number from v1 and v1’s value more stack objects and produces a list of size v1. ->list hp is an alias.

    `... OBn ... OB1 n 2list ==> ... [OBn ... OB1]`
  • len - Returns the number of items in the LST from v1 or the number of characters if v1 is a TXT object. size hp is an alias.

  • headn - Returns a list or substring made of the first v1 items of a list or string (respectively) at v2.

  • head hp - Same as 1 headn.

  • tailn - Returns a list or substring made of the last v1 items of a list or string (respectively) at v2.

  • tail hp - Same as 1 tailn

  • list-> hp Same as wake !

  • sort hp - Takes a LST object from v1 and returns a LST object where the components of v1 are sorted in ascending order. The items should all be TXT or VAL and of the same kind.

  • revlist hp - Takes a LST object from v1 and returns a LST object where the components of v1 are reversed in order. This allows for a descending sort with sort revlist. Works on TXT objecst too. Alias rev. This is actually very similar to -1 :*.

    4 range rev -> [3 2 1 0]
    ''poorcryptography'' rev -> yhpargotpyrcroop
  • get hp - Returns from v2 LST object the value of the list component whose position is specified by v1.

    [1.1 2.2 3.3 4.4] 3 get -> 3.3
  • getn - From a LST or SYM of a LST on v2, this function returns the value of the list component whose position is specified by v1 where v1 is a name from a named list. This can be specified as a SYM or TXT value. The alias is >>.

    [1.1 2.2 3.3 4.4::x y z w] |z getn -> 3.3
    [1.1 2.2 3.3 4.4::x y z w] ''y'' >> -> 2.2
    11 22 33 pointform |myp sto |myp |y >> -> 22
  • put hp - Takes list from v3 and a position from v2 and replaces the v3 item at v2 with the value of v1. This should also take a SYM of a list. If that is the case, the output should be nothing but the SYM should contain the updated list. If a LST is supplied as v3, then an updated list is returned.

  • putn - Put by name. Takes list from v3 and a position from v2 and replaces the named component of v3 at position specified with v2 with the value of v1. This command also takes a SYM of a list for v3. If that is the case, the output should be nothing but the SYM should contain the updated list. If a LST is supplied as v3, then an updated list is returned. The alias is <<.

    1 2 3 pointform |y 99 << -> [1 99 3]<x y z>
  • sub hp - Takes a string or list object from v3 and a start position from v2 and an end position from v1 and returns the sub-string or sub-list (respectively) from v2 to v1. If v1 is less than v2, an empty string or list is returned. Any positional value less than 1 is treated as 1. Any positional value greater than the len of v3 will be treated as the len of v3. The positional values are inclusive such that if v2 is 2 and v3 is 4, both the 2nd (and 3rd) and 4th position items will be part of the returned sub-object.

    ''abcdefghijklmnopqrstuvwxyz'' 19 21 sub -> ''stu''
  • repl hp - Takes a string or list object from v3 and a position from v2 and a replacement string or list object from v1 and replaces the portion of v3 starting at v2 with v1. If v2 is less than 1 or greater than the len of v3, the items are simply concatenated, v1+v3 and v3+v1 respectively.

  • pos hp - Takes a LST or TXT item from v2. If v2 is a LST then take an item from v1 and return the position v1 is found in v2. If v2 is a TXT itme, then v1 must be a TXT item and the value returned is the position where the substring v2 is found in v1.

  • sum - Produces the total of all values in a list. There is an alias tot hp.

    8 range sum -> 28

Comments

There is no special comment syntax per se, but there is a helpful alias provided to make commenting programs easier. The # is a special auto-spaced character that is mapped to the drop command. So to include a comment, simply put a text object on the stack and immediately follow it with a #. That’s basically an expensive null operation.

''This is a comment in the GeoGad idiom!''#

Using # interactively for drop is pretty handy too.

Evaluator Functions

  • help - Print a list of available functions. Not really too helpful at this point.

  • version - Some nice information about the program. This is executed on startup.

  • exit - Quit the interpreter. Ctrl-D or end of file also works.

  • quit - Alias of exit.

  • sto hp - Stores object X in the symbol table reference by symbol S. Note that PostScript’s def function has the input the other way around. PostScript is wrong.

    X S sto ->
  • eval - Takes X off the stack and uses that object as input. The results depend on what happens.

    X eval -> output
  • ! - Alias of eval. This is a special auto-spaced character.

    X! -> output
  • wake - List L becomes live so that subsequent evaluation will cause each object it contains to be evaluated in order.

    L wake -> L<!>
  • inert - Kill a list so that subsequent evaluations just produce the list itself.

    L<x> inert -> L
  • len - Counts how many objects are in a list or text literal.

    L len -> V
    T len -> V

Stack Functions

  • dropn hp - Drops n items from the list.

    Xn ... X1 n ->
  • drop hp - Alias of 1 dropn.

  • # - Alias of 1 dropn. Also a special auto-spaced character. Used immediately after text literals, this command produces a method for specifying comments in programs.

  • clear hp - Clear the entire stack.

    X... clear -> ** Empty Stack **

    Here’s a macro to map clear to "c" which can be handy. Note that CTRL-L will clear the screen, but not the stack.

  • depth hp - Count number of items on the stack.

    X... depth -> ... V
  • yank - Remove an item on level n of the stack and put it on level 1.

    Xn+1 Xn Xn-1 ... X1 n yank -> Xn+1 Xn-1 ...X1 Xn
  • placen - Take object on level 2 and place it at the level specified on level 1.

    Xn+1 Xn Xn-1 ... X1 n placen -> Xn+1 X1 Xn-1 ...
  • dupn hp - Get all the objects on levels n through 1 of the stack and put them on the stack again.

    Xn ... X1 n dupn -> Xn ... X1 Xn ... X1
  • pickn - Copy an object at level n and add the copy to the stack. There is an alias pick hp which does the exact same thing.

    Xn ... X1 n pickn -> Xn ... X1 Xn
  • swap hp - alias of 2 yank. PostScript cryptically calls this exch.

  • drop2 hp - Alias of 2 dropn.

  • drop3 - Alias of 3 dropn.

  • dup hp - Alias of 1 dupn.

  • dup2 hp - Alias of 2 dupn.

  • dup3 - Alias of 3 dupn.

  • rot hp - Alias of 3 yank.

  • over hp - Alias of 2 pickn.

Mathematical Functions

  • e_ - Constant with value of 2.7182818284590451.

  • pi - Constant with value of 3.1415926535897931.

  • mul - Produces the product of the value objects on levels 1 and 2. Item multiplication operator where the values are the focus. The common useful alias is *. Compare to :* where lists are the focus. SYMs are all resolved to their referent objects. Valid inputs from v1 and v2:

    • VAL VAL * → simple multiplication

    • TXT VAL * → replication of text into concatenated text, commutative input

    • LST_of_VALs VAL * → list of each list item multiplied by VAL, commutative input

    • LST_of_VALs LST_of_VALs * → pad smaller with 1 and multiply each ith pos

    If two lists are multiplied, the shorter one is padded with ones to match the other. In the case of two lists and both have list names, the longest list’s are used. Examples:

    2 2 * -> 4
    2 [1 2 3] * -> [2 4 6]
    31.831 |diameter sto |diameter |pi * -> 100.000035756
    [pi pi pi] pi inv * -> [1 1 1]
    [1 2 3] 2 * -> [2 4 6]
    [1 2 3] [1 2] * -> [1 4 3]  Pad the shorter list with ones.
    [2 2] [4 4 4::x y z] * -> [8 8 4]<x y z>
    [1 2] [1 2 3] * -> [1 4 3]
    4 ''ha'' * -> ''hahahaha''
    ''backwards'' -2 * -> ''sdrawkcabsdrawkcab''
  • neg - Alias of -1 mul.

    [3 pi -5] neg -> [-3.0 -3.14159265359 5.0]
    ''rats mood loot'' neg -> tool doom star
  • lmul - List multiplication operator where lists are the focus. The common useful alias is :*. Compare to * where items are the focus. All SYMs are resolved to their referent. The input requires only that v1 or v2 or both must resolve to a VAL. All other combinations are valid. A negative VAL as the multiplier will reverse the order of multiplied lists.

    • L V :* → replication of list items in list, commutative

    • T V :* → put text item in list with copies, commutative

    • V V :* → put v2 in list with v1 copies, not commutative

    Examples:

    [1 2 3] 2 :* -> [1 2 3 1 2 3]
    2 [1 2 3] :* -> [1 2 3 1 2 3]
    ''abc'' 2 :* -> [''abc''  ''abc'']
    4 3 :* -> [4 4 4]
    3 4 :* -> [3 3 3 3]
    [0 1] -3 :* -> [1 0 1 0 1 0]
  • add - Item addition operator where the values are the focus. The common useful alias is +. Compare to :+ where lists are the focus. SYMs are all resolved to their referent objects. Valid inputs from v1 and v2:

    • TXT TXT + → concatenate text

    • VAL VAL + → simple addition, commutative

    • LST_of_VALs VAL + → add VAL to all items in list (of values), commutative

    • LST_of_VALs LST_of_VALs + → add each corresponding position, commutative

    If two lists are added, the shorter one is padded with zeros to match the other. If two lists both have list names, the first list’s are used. Examples:

    2 2 + -> 4
    pi pi + 2deg -> 360
    [pi 10::a b] |Q sto -180 2rad Q.a + -> 0
    [4 8] 3 + -> [7 11]
    [4 8] [2 3] + -> [6 11]
    [4 8] [2 3] + -> [6 11]
    [4 8 3] [2 3] + -> [6 11 3]
    [4 8] [1 2 3] + -> [5 10 3]
    [0 4] [1 2 3] + -> [1 6 3]
    [1 2 3::a b c] [2 2 2::x y z] + -> [3 4 5]<a b c>
    ''xed'' ''.ch'' + -> ''xed.ch''
  • ladd - List addition operator where lists are the focus. Compare to + where items are the focus. Given the existence of v1 and v2, all input possibilities are valid. Only SYMs that ultimately refer to LSTs are resolved prior to the actual operation. Name space lists are lost.

    • L ? :+ → append item to end of list

    • ? L :+ → prepend item to front of list

    • L L :+ → concatenate lists in order [v2 v1]

    • notL notL :+ → make list containing [notL notL]

    Examples:

    [4 8] 7 :+ -> [4 8 7]
    4 [4 8] :+ -> [4 4 8]
    [1 2 3] |Q sto |Q 4 :+ -> [1 2 3 4]
    [2 ''two''] [3 3 3] :+ -> [2 ''two'' 3 3 3]
    [3 3 3] [2 2] :+ -> [3 3 3 2 2]
    ''listify'' ''me'' :+ [''listify'' ''me'']
  • sub - Alias of neg add.

  • - - Alias of sub.

  • ++ - Alias of 1 add.

  • -- - Alias of 1 sub.

  • div - Produces the quotient of the value objects on levels 1 and 2.

    V V div -> V
  • / - Alias of div.

  • inv - Alias of 1 swap div.

  • idiv - Produces the integer division of the value objects on levels 1 and 2.

    v2 v1 idiv -> v2/v1
  • mod - Produces the modulus of the value objects on levels 1 and 2.

    Va Vb mod -> v1_mod_vb
  • abs - Absolute value.

    V abs -> V
  • floor - The lowest whole number nearest to V.

    V floor -> V
  • ceil - The highest whole number nearest to V.

    V ceil -> V
  • pow - The value on level 2 raised to the power of the value on level 1.

    V V pow -> V
  • xroot hp - Computes v1 th root of v2. Alias of inv pow

    27 3 xroot -> 3
  • sqrt - The square root of the value at v1.

    V sqrt -> V
  • sq hp - Square the value at v1. Alias of dup *.

  • exp - The constant e raised to the power of Vx.

    Vx exp -> V
  • logbase - The Vb base log of Vx.

    Vx Vb logbase -> V
  • log - Natural log.

    V log -> V
  • log10 - Base 10 log.

    V log10 -> V
  • radmode - Changes angle mode to radians. Trig functions assume radian inputs and inverse trig functions produce radian angles. This will create the variable _gg_angmode to maintain this state; it can be set manually too with sto and unsto.

  • degmode - Changes angle mode to degrees. Trig functions assume degree inputs and inverse trig functions produce degree angles. This is the default and the assumed mode when the state variable is not set.

  • 2deg - Converts a value from radians to degrees. If the value is a list containing no other type but VAL for the first three positions (or fewer), then the list is interpreted as containing degrees, arcminutes (if present), and arcseconds (if also present). Empty lists return zero and more than three values in a list are ignored.

    Vr 2deg -> Vd
    [45.0 30.0 0.0::degrees arcminutes arcseconds] 2deg -> 45.5
  • 2rad - Converts a value from degrees to radians. This also works like 2deg with lists of degree, arcminutes, and arcseconds. This does not need the angle mode set to radians since it is already explicit.

    Vd 2rad -> Vr
    [180 0 0] 2rad -> 3.141592653589793
  • 2dms - Convert the angle supplied to a list representing degrees, arcminutes, and arcseconds. This function defaults to treating the supplied angle as degrees but if radmode has been called, it will assume radians.

    degmode 45.5 2dms -> [45.0 30.0 0.0::degrees arcminutes arcseconds]
    45.5 2rad radmode 2dms -> [45.0 30.0 0.0::degrees arcminutes arcseconds]
  • sin - Sine of the angle argument specified. Input units are assumed to be degrees unless radmode has been called.

    V sin -> V
    90 sin -> 1.0
    degmode 30 sin radmode pi 6 / sin -> 0.5 0.5
  • cos - Cosine of the angle argument specified. Input units are assumed to be degrees unless radmode has been called.

    V cos -> V
    0 cos -> 1.0
  • tan - Tangent of the angle argument specified. Input units are assumed to be degrees unless radmode has been called.

    V tan -> V
    45 tan -> 1.0
  • asin - Arcsine. Also respects radmode and degmode for angle modes. Input needs to be between -1 and 1.

    V asin -> V
  • acos - Arccosine. Also respects radmode and degmode for angle modes. Input needs to be between -1 and 1.

    V acos -> V
  • atan - Arctangent. Also respects radmode and degmode for angle modes.

    1 atan -> 45
    5 inv atan 4 * 239 inv atan - 4 * 2rad -> 3.14159265359

Statistics Functions

  • rand hp - Returns a random value between 0 and 1.

  • rdz hp - Takes v1 off the stack and uses it to set the random seed. If the stack is empty or v1 is zero, use current time. Set this with known objects to get repeatable random sequences.

  • shuffle - Takes a list item from v1 and returns a list of the same objects in a completely random order. The following will shuffle the entire stack.

    depth 2list shuffle wake !
  • fact hp - Factorial of the value at v1. It is helpful to remember that the factorial is the number of permutations of n distinct objects. For example, the number of anagrams of a word is the factorial of the word length (if all letters are different - if not, look into "multisets"). This function name was actually HP28 syntax which was preserved on the HP48. It’s useful here since the normal syntax for factorial is busy doing other important things. This is not (yet) a real Gamma function. Factorials get big in a hurry so avoid large numbers; 170 is the max allowable for now.

    18 fact -> 6.40237370573e+15
  • comb hp - Number of possible combinations of v2 items taken v1 at a time. A poker hand is an example. Imagine 52 (v2) distinct cards drawn 5 (v1) at a time. Order does not matter and each is only used once. This creates the possibilities for around 2.6 million possible poker hands.

    fact(v2)/(fact(v1)*fact(v2-v1))
    52 5 comb -> 2598960
  • perm hp - Number of possible permutations of v2 items taken v1 at a time. In real life, permutation often answers the question, how many ways can N items be ordered? For example, if you want to know how many ways there are arrange a family of 4 at a 4 person table, use 4 for v1 and v2. If there are 8 dogs racing and you want to know how many different ways the top 3 could be filled, use 8 for v2 and 3 for v1. If a keysafe has 10 buttons, 4 of which must be set in the correct order to open, how many possible ways can this be tried?

    fact(v2) / fact(v2-v1)
    4 4 perm -> 24
    8 3 perm -> 336
    10 4 perm -> 5040
  • distgauss - Takes VALs from v1 and v2 and returns a value randomly generated by a function that produces values which are distributed in a Gaussian distribution. The v2 VAL is the mean of the distribution. The v1 VAL is the distribution’s standard deviation. There is an alias ndist hp.

    |[.5 .2 distgauss::!] 100000 repeat 100000 2list dup
        mean swap sdev -> 0.500337044014 0.200301690936
  • distexp - Takes VAL from v1 and returns a value randomly generated by a function that produces values which are distributed in an exponential distribution. The v1 VAL is Lambda which is a parameter of this kind of distribution. The formula is f(x,Lambda)= Lambda * exp(-Lambda * x) The mean is 1/Lambda and the variance is 1/(Lambda*Lambda).

    |[.75 distexp::!] 100000 repeat 100000 2list
    dup mean inv swap var inv sqrt -> 0.743701781531 0.742319134654
  • distweibull - Takes VALs from v1 and v2, and returns a value randomly generated by a function that produces values which are distributed in a Weibull distribution. This is often used to model processes related to failure rates, survival analysis, and reliability. The v2 VAL is Lambda which is the scale parameter for this kind of distribution. The v1 VAL is k which is the shape parameter. Both must be greater than zero. If k is 1 this is equivalent to the exponential distribution. If k is 2 it is equivalent to the Rayleigh distribution. A value of k < 1 indicates that the failure rate decreases over time. This happens if there is significant "infant mortality", or defective items failing early and the failure rate decreasing over time as the defective items are weeded out of the population. A value of k = 1 indicates that the failure rate is constant over time. This might suggest random external events are causing mortality, or failure. A value of k > 1 indicates that the failure rate increases with time. This happens if there is an "aging" process, or parts that are more likely to fail as time goes on.

    |[.75 1 distweibull::!] 100000 repeat 100000 2list mean -> 0.751247734665
  • max hp - Returns the maximum value of a list from v1. If the list contains symbols, they should get resolved at least one level deep. Simple lists should work as expected. Extremely complex lists may be an adventure.

    [2 e_ 3 pi 1] -> pi
    |rand 100 repeat 100 2list max -> 0.998288151032
  • min hp - Finds the minimum value in a list at v1. Similar behavior to max.

    [4 e_ 3 7] min -> e_
    [4 e_ 3 2 7] min -> 2
  • avg - Produces the average, or to be precise, the arithmetic mean of all value items contained in a list at v1. There is an alias mean hp.

    8 range avg -> 3.5
  • weightedmean - The weightedmean function takes a list of numbers to be averaged from v2 and a list of weights to bias the result by. The lists must be or resolve to VALs and they must be the same length. This is commonly used to calculate weighted grades. If a student gets grades of C, A, A, D, B (corresponding to [2 4 4 1 3]) in classes of 4, 3, 3, 4, 3 credits respectively, the weighted average of these two lists will give a GPA adjusted for the "importance" based on credits. This could also be used to make product recommendations based on how well rated an item is by a list of people (list v2) and how closely each of those people match the user’s previous tastes (list v1, the weights).

    [2 4 4 1 3] [4 3 3 4 3] weightedmean -> 2.64705882353
    [2 4 4 1 3] mean -> 2.8
    [76  89  83  79  91  95  82  69  66  75  80  88 ]
    [2.2 2.4 3.1 2.5 3.5 3.6 2.5 2.0 2.2 2.6 2.7 3.3] weightedmean -> 82.3834355828
    [76  89  83  79  91  95  82  69  66  75  80  88 ] mean -> 81.0833333333
  • var hp - Calculate the sample variance of a list of values. Use this when some of the members of the entire population are not represented.

    [2.2 2.4 3.1 2.5 3.5 3.6 2.5 2.0 2.2 2.6 2.7 3.3] var -> 0.285151515152
    [76 89 83 79 91 95 82 69 66 75 80 88] var -> 77.1742424242
  • pvar hp - Calculate the population variance of a list of values. Use this when all of the members of the entire population are represented.

    [2.2 2.4 3.1 2.5 3.5 3.6 2.5 2.0 2.2 2.6 2.7 3.3] var -> 0.261388888889
    [76 89 83 79 91 95 82 69 66 75 80 88] var -> 70.7430555556
  • sdev hp - Calculate the sample standard deviation of a list of values. Use this when some of the members of the entire population are not represented. This is the square root of the sample variance.

    [2.2 2.4 3.1 2.5 3.5 3.6 2.5 2.0 2.2 2.6 2.7 3.3] sdev -> 0.533995800687
    [76 89 83 79 91 95 82 69 66 75 80 88] sdev -> 8.78488716059
  • psdev hp - Calculate the population standard deviation of a list of values. Use this when all of the members of the entire population are represented. This is the square root of the population variance

    [2.2 2.4 3.1 2.5 3.5 3.6 2.5 2.0 2.2 2.6 2.7 3.3] var -> 0.511262055006
    [76 89 83 79 91 95 82 69 66 75 80 88] var -> 8.41088910613
  • cov hp - Calculate the sample covariance of two lists of values taken from v1 and v2. The two lists should be the same length. Use this when some of the members of the entire population are not represented.

    [2.2 2.4 3.1 2.5 3.5 3.6 2.5 2.0 2.2 2.6 2.7 3.3]
    [76  89  83  79  91  95  82  69  66  75  80  88 ] cov -> 3.85303030303
  • pcov hp - Calculate the population covariance of two lists of values taken from v1 and v2. The two lists should be the same length. Use this when all of the members of the entire population are represented.

    [2.2 2.4 3.1 2.5 3.5 3.6 2.5 2.0 2.2 2.6 2.7 3.3]
    [76  89  83  79  91  95  82  69  66  75  80  88 ] pcov -> 3.53194444444
  • corr hp - Calculate the sample Pearson’s correleation coefficient (r) between two lists of values taken from v1 and v2. The two lists should be the same length. The Pearson product-moment correlation coefficient (PMCC) is a numerical value between -1 and 1 that measures the strength of the linear relationship or association between two variables.When r is closer to 1 it indicates a strong positive relationship. A value of 0 indicates that there is no relationship. Values close to -1 signal a strong negative relationship between the two variables. The basic composition of the Pearson PMCC is the covariance of the two lists divided by the product of each list’s standard deviation. Note: It is possible to specify the correlation coefficient in terms of population statistics. That was done and the actual answers produced were the same indicating that the differences (the 1/(1-n) vs 1/n ) cancel in this process. This is why there is not "population correlation coefficient" even though such a thing has a name, rho. The HP48 also scrupulously had population and sample statistics for everything except corr. And those folks knew what they were doing!

    [2.2 2.4 3.1 2.5 3.5 3.6 2.5 2.0 2.2 2.6 2.7 3.3]
    [76  89  83  79  91  95  82  69  66  75  80  88 ] corr -> 0.821350253246
  • tanimoto - The tanimoto function takes a list of properties of item A from v2 and a list of properties of item B from v1 and calculates the Tanimoto similarity coefficient. The idea is to compute a measure of how similar these things A and B are based on their characteristics. For example, if you’re an alien naturalist and you find a mystery animal with "horns", "tail", "beard", "red", and "hoof", and you want to see if its more like a zebra ("tail", "stripes", "hoof", "mane") or a goat ("horns", "tail", "hoof", "beard") this might be helpful. In recommendation engines, this can be used to find people with similar properties. There is much confusion aboout the origin and use of this. It seems to be a generalization of the Jaccard Index applicable to sets, which is what this function assumes. The formula used here is T= Ni/(Na+Nb-Ni) where Ni is the number of items in both lists or the count of the intersection, Na is the number of items in list A, and Nb is the number of items in list B. Dissimilarity, a distance metric, can be acheived with 1 rot rot tanimoto -. The properties can be specified as TXT or VAL or SYM. If they are SYM, they are resolved.

    [''horns'' ''tail'' ''beard'' ''red'' ''hoof''] |devil sto
    devil [''tail'' ''stripes'' ''hoof'' ''mane''] tanimoto -> 0.285714285714
    devil [''horns'' ''beard'' ''tail'' ''hoof''] tanimoto -> .8
  • entropy - Calculate the Shannon entropy for a list of items. This is a measure of how surprising a random selection of the data is. It can be used to measure the amount of disorder in a set. Lowering entropy during an iterative classification process could be a sign that the data is becoming better organized. Symbols do get resolved although other data stays as is. The formula is H(X)=-sum[i=1,n](p(x)*log2(p(x))) where p(x) is the probability that X is in state x. The n is the number of unique items in the original list. The log2 aspect causes this function to return output measured in bits of entropy. This entropy calculation is likely not correct for cryptographic keys and passphrases since the probabilities of a certain value are generally lower than this process assumes (by counting extant examples).

    [2.2 2.4 3.1 2.5 3.5 3.6 2.5 2.0 2.2 2.6 2.7 3.3] entropy -> 3.25162916739
    [76 89 83 79 91 95 82 69 66 75 80 88] entropy -> 3.58496250072
    [''p'' ''a'' ''s'' ''s'' ''w'' ''o'' ''r'' ''d''] entropy -> 2.75
    [''3'' ''T'' ''^'' '','' ''d'' ''9'' ''9'' ''w''] entropy -> 2.75
    100000 range entropy -> 16.6096404744

Functions marked hp are also found on the HP48 in a largely consistent format.

Program Flow Functions

  • repeat - Takes objects X and n off the stack and evaluates object X repeatedly n times.

    X n repeat -> ...
    ''pig'' 3 repeat   3 2list -> LST:[TXT:pig, TXT:pig, TXT:pig]

Note that X is taken off the stack as is. If you try to put some kind of dynamic thing on the stack, it will be evaluated once and all the repeated iterations of it will be identical (i.e. the awake list will not be evaluated any more times). To have the sequence execute on every iteration, protect it.

|[rand 6 * 1 + floor::!] |roll_dice sto -> ** Empty Stack **
|roll_dice 5 repeat -> VAL:4.0 VAL:4.0 VAL:1.0 VAL:6.0 VAL:1.0
|[roll_dice::!] 5 repeat -> VAL:6.0 VAL:5.0 VAL:5.0 VAL:6.0 VAL:4.0
  • if - Removes Xaction and then Xtest from the stack and if Xtest is true, evaluates the object Xaction.

    Xtest Xaction if -> ...
    |[rand 6 * 1 + floor::!] |roll_dice sto -> ** Empty Stack **
    roll_dice roll_dice + 2 == ''snake eyes!'' if -> TXT:snake eyes!
    ''(1/36 of the time)'' #
  • ifelse - Removes Xgood, Xbad, and Xtest from the stack and if Xtest is true, evaluates Xgood. If Xtest if false, Xbad is evaluated.

    Xtest Xgood Xbad ifelse -> ...
    rand .5 > ''heads'' ''tails'' ifelse -> TXT:tails (or heads)
  • while - Xtest is removed from the stack once initially.

    Xtest Xaction while -> ...

Xaction is removed from the stack once initially. The loop process begins with the evaluation of Xtest. The resulting level one result is removed. If this result is true Xaction is executed. This is repeated until Xtest evaluates false. This means that when the loop should exit, an object should be placed on the stack by the action which will make the test evaluate false. For example:

13 |[dup 16 lt::!] |[dup ++::!] while -> 13 14 15 16
|[rand 6 * 1 + floor::!] |roll_dice sto -> ** Empty Stack **
roll_dice |[dup 5 le::!] |roll_dice while -> 3 3 1 3 6
  • gt - Greater than. 1 if V2 is greater than V1, 0 if not.

    V2 V1 gt -> 1|0
  • lt - Less than.

    V V lt -> 1|0
  • le - Less than or equal to.

    V V ge -> 1|0
  • ge - Greater than or equal to.

    V V ge -> 1|0
  • eq - Equal to.

    V V eq -> 1|0
  • == - Alias of eq.

  • same - hp Alias of eq.

  • ne - Alias of eq not.

  • not hp - Logical not.

    X not -> 0|1
  • and hp - Logical and.

    X X and -> 0|1
  • nand - Alias of and not.

  • or hp - Logical or.

    X X or -> 0|1
  • nor - Alias of or not.

  • xor hp - Alias of dup2 or 3 placen not swap not or and.

  • xnor - Alias of xor not.

Flow Control

  • range - Takes a positive integer off the stack and returns a list containing 0 through that number minus 1 in order. To get 1 through N, try something like this:

    4 range -> [0 1 2 3]
    4 range 1 + -> [1 2 3 4]
  • repeat -

    0 3 repeat -> 0 0 0
  • for - Take two objects off the stack comprising of a list and an action object. For each item in the list, put that item on the stack and then evaluate the function object.

    OBlist OBaction for -> <action evaluated for all in list>

Notice that this is more like the Python for or the Perl foreach than the C for. If you want a C for loop such as:

for (i=20; i<50; i+=6) {leave_num_on_stack();}

try this:

20 |i sto |[i 50 <::!] |[i i 6 + |i sto::!] while
  • foreach - Synonym for for.

  • filter - Take two objects off the stack, an object to perform some function on other objects and a list of those other objects. For each item in the list, put that item on the stack twice and then evaluate the function object. Then do an if. If the function object made a true value, then the copy will remain, if not, it goes. At the end, put all the stack items into a list except for the first n items where n is the size of the stack before the filter command or its arguments were introduced. In English, this means that if you have a stack with some original items, those should remain on the stack after the filter, but all of the objects the mapped function may produce will be rounded up into a list. Now the weird thing is that it is possible to have your filter function eat more levels off the stack than just the one of the list. In that case, you could have a stack be smaller than n+1 items where n is the number of original items. Example:

    1 |[dup 30 <::!] |[dup ++::!] while 0 2list |[7 mod not::!] filter ->
    LST:[VAL:7.0, VAL:14.0, VAL:21.0, VAL:28.0]
  • map - Take two objects off the stack, an object to perform some function on other objects and a list of those other objects. For each item in the list, put that item on the stack and then evaluate the function object. At the end, put all the stack items into a list except for the first n items where n is the size of the stack before the map command or its arguments were introduced. In English, this means that if you have a stack with some original items, those should remain on the stack after the map, but all of the objects the mapped function may produce will be rounded up into a list. Now the weird thing is that it is possible to have your mapped function eat more levels off the stack than just the one of the list. In that case, you could have a stack be smaller than n+1 items where n is the number of original items.

    OBlist OBaction map -> [list of action applied to each in OBlist]
    |[25.4 *::!] |tomm sto ->
    1 |[dup ++::!] 4 repeat 5 2list |tomm map -> [25.4 50.8 76.2 101.6 127.0]

Here’s a comparison of some different ways to make a random direction vector. All of these methods produce the same result.

|[rand .5 -::!] 3 repeat 3 2list
3 range |[drop rand .5 -::!] for 3 2list
3 range |[drop rand .5 -::!] map
[-.5] 3 :* |[rand +::!] map

Geometry Functions

Notes for commands to actually work with geometry. Woo!

Entity Construction

This basically makes a 3 item list from 3 numbers. The namespace is just a niceity and not required for anything.

1 2 3 pointform -> [1 2 3::x y z]
1 2 3 p -> [1 2 3::x y z]

Make a line entity. This adds a line entity to the model consisting of the two endpoints supplied. The entity ID of the line is returned as a numeric value.

[1 2 3] [5 6 7] line -> <entID>
1 2 3 p 5 6 7 p l -> <entID>

Make a tri entity in a similar way to how line works.

[1 2 3] [5 6 7] [9 10 11] tri -> <entID>
1 2 3 p 5 6 7 p 9 10 11 p t -> <entID>

Here’s a nice demo of a triangle that I’ve been using.

10 10 0 p 100 10 0 p l 100 10 0 p 50 100 0 p l 50 100 0 p 10 10 0 p l
10 10 0 p 100 10 0 p 50 100 0 p t

View Control

The viewshow function will print out all the parameters used by the viewing system, including to2d and the SVG/HTML output.

viewshow ->
vshow ->

The defaults look like this.

 View Properties
 ---------------
SVGOUTFILE=/tmp/gg.html
CAMERA=0,0,1        {camset}
TARGET=0,0,0        {tarset}
OPACITY=True       {hlr,hide}
FACELINES=False     {facelines}
VLINEWIDTH=2.00 {vlw,viewlinewidth}
VREFRESHMS=1500    {refresh,viewrefreshms}
VBOX=(900,900)     {viewbox[xy]}
VTRAN=(300,-450)    {vtran[xy],viewtran[xy]}
VSCALE=(1,-1)   {vscale[xy],viewscale[xy]}

Formerly there were commands such as camset that set all these variables. These were all replaced with a new system which uses GeoGad’s own ability to store complex variables. When the system first tries to produce a graphical output, it creates a view variable called vw which is structured like this.

[
    ''/tmp/gg.html''
    [0.0 0.0 1.0::x y z]
    [0.0 0.0 0.0::x y z]
    1.0
    0.0
    2.0
    1500.0
    [900.0 900.0::x y]
    [300.0 -450.0::x y]
    [1.0 -1.0::x y]
::fi ca ta op fl lw ms bx tr sc]

This allows the existing variable syntax features to be used. For example, to check the existing value of and then set a new output file.

vw.fi ->
|vw.fi ''/tmp/snapshot.html'' sto ->

Note that these settings won’t appear to take effect immediately, but the next time the display is updated, they should. You can used refresh to hasten that.

Here are the parameter names and their meaning.

  • vw.fi - Output filename. This is where the HTML file is saved which allows the user to use a browser for an interactive display.

  • vw.ca.x - Camera X coordinate. Camera is where the viewer is looking from.

  • vw.ca.y - Camera Y coordinate.

  • vw.ca.z - Camera Z coordinate.

  • vw.ta.x - Target X coordinate. Where the viewer is looking to.

  • vw.ta.y - Target Y coordinate.

  • vw.ta.z - Target Z coordinate.

  • vw.op - Opacity flag. 0 is off. 1 is on. This controls whether the hidden line removal algorithm is activated when running the renderer. Think of it as a "wireframe" mode if set to 0.

  • vw.fl - Facelines. This controls whether trifaces will get 3 lines automatically rendered. This can be useful to find lost trifaces that become separated from their visible components or otherwise corrupt. 0, the default, is off, and 1 is on.

  • vw.lw - Line width. This is the width the lines are rendered by default. This is affected by scale and can be overridden with attribute tags.

  • vw.ms - Milliseconds. This specifies the reload delay encoded into the output HTML. The output HTML tries to reload itself after a certain time interval. This is where that is specified. The default is 1500 milliseconds or 1.5 seconds.

  • vw.bx.x - Output box X size. This is the width in pixels of the display area on the browser.

  • vw.bx.y - Output box Y size. This is the height in pixels of the display area on the browser.

  • vw.tr.x - Translate X value. This is the distance the model is moved horizontally (right generally being positive). This value is used to correctly position the display so that it is plotting the magnitude of values for the geometry you want to see. For example, if the display box is plotting model values of X between 50 and 100 and your model’s lowest X is -.5 and the highest is -.25, you will not see it. This parameter helps set the view port correctly.

  • vw.tr.y - Translate Y value. This is just like the X value but it is usually negative. This may be changed in the future so that the insanity of SVG’s top is 0, positive is down will be completely concealed. But right now if you want to move your model down in the display, use a bigger magnitude negative number.

  • vw.sc.x - Scale factor X. This controls how the display is scaled. If the view port is set up to plot 0 to 100 units in the X, and your model spans from -500 to 500, then you’ll need to change this by zooming out, i.e. a smaller scale factor.

  • vw.sc.y - Scale factor Y. Same as the X but, like the translate parameter, it is negative to account for the fact that SVG is upside down. If you use a positive value, the model will most likely be upside down.

To set the camera position. (The default is [0 0 1].)

2 -4 1 p |vw.ca sto ->

To set just the Y coordinate.

-4.5 |vw.ca.y sto ->

Query Entities

Easily getting at the most recent entity should be quite useful.

last -> <entID>

The order of entity creation should not be surprising. It should be possible to go back to arbitrary entities.

2 lastn -> <entID> # The penultimate entity.

It is often handy to do something with all of the entities. The all function returns a list of all entities.

all -> [<entID1>...<entIDn>]
allent -> [<entID1>...<entIDn>]

Entity Tags

Keeping track of geometrical entities can be complicated. GeoGad allows for every entity to be given user defined tags. These tags can provide the functionality other programs achieve through groups, sets, layers, etc. Even entity attributes like "red" or "dashed" can reasonably be kept as tags allowing for very sophisticated management (see Attribute Tags).

The general syntax of the tag function is an entity ID or list of entity IDs is first supplied. Then a tag text item or a list of tag text items. The text items are added to the tag list of each entity.

<entID> ''roof'' tag ->
[<entID> <entID> <entID>] ''roof'' tag ->
[<entID> <entID>] [''roof'' ''house''] tag ->
last ''doortop'' tag ->
all ''model'' tag ->

If you don’t want to lose the entity IDs from the stack, use dup.

<entID> dup ''roof'' tag -> <entID>
100 0 0 p move ->

Tags can also be removed from an entity.

<entID> ''roof'' untag ->
[<entID> <entID>] [''roof'' ''house''] tag ->

Any specified entities which are tagged with any of the specified tags will have those tags removed.

To remove all tags from an entity or list of entities, use the detag function.

<entID> detag ->
[<entID> <entID>] detag ->

Use all detag to remove all tags in the model.

Often when copying entities, it is helpful to change one of the tags to distinguish the old and new sets. To do this use the chtag function.

<entID> ''old'' ''new'' untag ->
[<entID> <entID>] ''old'' ''new'' tag ->

Here is an example of copying all entities tagged with 'left' and tagging the new set with 'right' instead.

''left'' dup tag? edup swap ''right'' chtag ->

Querying By Tags

The tag? function can filter on tags. Note that a list should always be returned for consistency, even if it is empty.

''roof'' tag? -> [<entID> <entID>]
''goof'' tag? -> []

To find entities tagged by multiple tags, use a list. This will find any entities tagged with both "roof" and "house".

[''roof'' ''house''] tag? -> [<entID> <entID>]

This usage implies an and operation where all tags supplied must apply to the entity. To achieve an or effect use a construction like this.

[''roof'' ''house''] tag? [''roof'' ''barn''] tag? + -> [<entID> <entID>]

There is also a function for not tagged with the supplied tags. This means all of the tags must be absent if more than one are specified. For the simple case, this will produce a list of entities which do not have the "house" tag.

''house'' nottag? -> [<entID> <entID>]

The following will produce a list of entity IDs which are tagged with neither "roof" nor "house".

[''roof'' ''house''] nottag? -> [<entID> <entID>]

Attribute Tags

Attribute tags generally behave just like any tags, but they may have additional magical properties relating to display rendering or other indirect things. The format for an attribute tag is a single letter followed by an @. Using an @ anywhere else in a tag or for any other reason is probably a bad idea. The syntax requires a letter designating the type of attribute tag, then the @ designating it as an attribute tag, and then the value of the attribute. For example, C@red would mean "color attribute red". Currently the supported attributes are:

  • C - Color can use any named color from usr/share/X11/rgb.txt such as C@orchid or a web color like C@#ce1337 or even something like C@rgb(80%,30%,50%) or C@rgb(255,200,144). The SVG property is stroke. If there are spaces in the color name, just remove them; for example, darkorchid seemed to work. The color can be set to none or off if you do not want the entity to display.

  • W - Specifies line width or weight. It probably is measured in pixels by default. It is normal to specify this with something like W@3.5. Values can be any of the goofy things that CSS allows like W@2.5px or W@1em. The SVG property is stroke-width.

  • S - This specifies how the line will be drawn if it is not continuous. This is for dotted or dashed lines. The value here is a comma separated list of values that mean S,NS,[S,NS,…] where S is the distance to stroke the line and NS is the distance to skip not stroking. For a moderate dotted line, this will be fine S@3,3. For a dashed line, S@5,1.5 works. For a technical center line, use S@5,5,20,5. The SVG property is stroke-dasharray.

  • O - Opacity. This controls the alpha channel of the object. This can be in percent where 100% is completely opaque, such as O@100%. It can also be specified as a ratio from 0 to 1 such as O@.25. The SVG property is stroke-opacity.

Note
When setting an attribute tag, if an attribute tag of the same variety already exists for an entity, it is removed to accommodate the new one. If, for example, you have an entity currently tagged C@green and you set a tag of C@red, the C@green will be removed.

Showing Memory Model Data

The mm function is used to check the status of the memory model which includes the tags for each entity. This shows entity ID, point geometry and tag list for all entities in the memory model.

mm ->

The mmitem function will dump the memory model only for the supplied entity or entity list.

[<entID1>...<entIDn>] mmitem ->

Finding Duplicated Entities

If an entity is using all of the exact same points as another entity, it is not very useful, especially if the user does not realize these entities are lurking there. If you want to find if a given entity or list of entities has any duplicates use the doubles function.

[<entID>...<entID>] edup? -> [<entID>...<entID>]
<entID> edup? -> [<entID>...<entID>]
<entID> doubles -> [<entID>...<entID>]

This should show any other entities which share the entities' points geometrically. If you specify entities which are themselves duplicates, only the first one in the list will be searched for other duplicates. This looks like the following example.

gg--> [2 14] mmitem
2: Line [50.00 100.00 0.00] [10.00 10.00 0.00]
14: Line [50.00 100.00 0.00] [10.00 10.00 0.00]
      ** Empty Stack **
gg--> [2 14] doubles
      /--------------------
      | (1) LST:[VAL:14.0]
      \--------------------
gg--> # [14 2] doubles
      /--------------------
      | (1) LST:[VAL:2.0]
      \--------------------

To get rid of any duplicates you may have been unaware of, use all doubles ~.

Entity Decomposition

Get the geometry for a supplied <entID>.

<entID> pts -> <entPointLST>
22 pts -> [1 2 3::x y z] [5 6 7::x y z]
33 pts -> [1 2 3::x y z] [5 6 7::x y z] [9 10 11::x y z]

A list of entity of ids is also supported but the output is not in a list. This could be changed in the future.

[<entID1> ... <entIDn>] pts -> [ <entPointLST> ]
[1 2] pts -> [1 2 3::x y z] [5 6 7::x y z] [8 9 10::x y z] [11 12 13::x y z]

To get components of a list, it is often easiest to use the aliases for getn and putn. If these are called with numbers to specify position, then they will actually call get and put.

[9 10 11] 1 >> -> 9

To use with lists with named parts, you can specify the position with a symbol or a text.

11 22 33 pointform |x >> ->  11.0
11 22 33 pointform ''y'' >> -> 22.0

This for assignment.

[9 10 11] 5 2 << -> [9 5 11]
11 22 0 p |myp sto |myp |z 33 << myp -> [11.0 22.0 33.0]<x y z>

See get, put, getn, and putn in the list section.

Measurement

To find the distance between two points use the dist command.

[0 0 0] [10 10 10] dist -> 17.3205

To find the length of a line first decompose it with the pts command like this.

3 pts dist -> <distance_VAL_of_entity_#3>

Angle measurement can be done with the angle function. Given 3 points (LSTs of three VALs) on stack level 3, 2 and 1, compute the angle in degrees between the two supplied points of level 3 and 2, and the two supplied points of level 2 and 1. For example:

[5 5 0] [0 0 0] [-5 5 5] angle -> 90

To get the angle between two line entities which share a common end point, use something like:

<entID1> pts <entID2> pts drop angle ->

If the line entities do not share an endpoint, wait until the intersection function is completed.

Midpoint

Compute midpoint of a line.

[1 2 3] [5 6 7] midp -> [3 4 5]
[1 2 3] [5 6 7] % -> [3 4 5]

Compute point some ratio along line.

[0 0 0] [8 8 8] .25 midpn -> [2 2 2]
[0 0 0] [8 8 8] .25 %n -> [2 2 2]

Entity Destruction

Erase an entity specified by entity id or a list of entity IDs.

<entID> erase ->
<entID> ~ ->
[<entID1>...<entIDn>] erase ->

If you want to erase all entities from the model use the all command in conjunction with erase.

all erase->
all ~ ->

Copy

The entdup function takes the supplied entity ID and makes a copy of that entity. The copied entity uses the exact same points (whose reference count increases by one). The entity ID of the new entity is returned to the stack. The edup function is an alias of entdup.

<entID> entdup -> <new_entID>
<entID> edup -> <new_entID>

This function can also take a list of entity IDs in which case the returned entity IDs are also in a list.

[<entID1>...<entIDn>] entdup -> [<new_entID1>...<new_entIDn>]

The set of all entities can be copied with the all command.

all entdup -> [<new_entID1>...<new_entIDn>]

All tags an entity has are replicated in the duplicated entity. If this is not wanted, use the detag function.

<entID> edup dup detag -> <new_entID>

Move

Move an entity’s points by a supplied offset. This should politely not add new points if you’re doing something like moving p1 of a line to p2. In that case, p2 should remain untouched and what was p2 will get a new point if there isn’t one already in the model.

<entID> [x y z] move ->
<entID> [x y z] m ->

If a list of <entID> VAL is supplied with an offset, then each of the entities will be moved.

[ <entID>...<entID> ] [x y z] move ->

If you include the same entity more than once, then it is moved for each occurrence in the list

Rotate

Rotate one or more entities around a provided axis by a provided angle. This function takes 3 items off the stack. In order of entry, the third item is either an entity ID value or a list of one or more entity IDs. These are the entities which will be modified by the rotation. The second item is the axis of rotation. It can be an entity ID value of a line entity (tri entities are not supported) or it can be a list containing precisely two lists which each contain three values. This list contains two coordinate lists for points on (defining) a line. This line is the axis of rotation. Finally, the first item popped off the stack (last entered) is the angle in degrees of the rotation. An example is [2 4 13] [[0 0 0][0 0 1]] 45 rotate ->, which would rotate entities 2, 4, and 13 by 45 degrees counter clockwise when seen looking from the second reference axis point ([0 0 1]) to the first ([0 0 0]).

<entID> <line_entID> <angle> rotate ->
[<entID1>...<entIDn>] <line_entID> 45 rotate ->
<entID> [[5 5 0][5 5 1]] 45 rotate ->
[<entID1>...<entIDn>] [[5 5 0][5 5 1]] 45 rotate ->

File Operations

Sourcing

Load stack items/script from file as if typing it in.

''/home/xed/dodecahedron.gg'' source -> ...
Note
The source command can be used for any GeoGad commands. Any kind of script or module you wish to prepare and deploy can be put in a file as a sequence of commands and sourced in at any time.

Memory Model Save

In theory there are very clever ways to do this, but for right now, the system will output a file that can be used with source to roughly replicate the current geometry model in memory. It is not exact because entity and point IDs can become reassigned, however, the geometry should be exact. This saved file can also be edited in a text editor to achieve desired effects.

mmsave ->

The file that is saved to is currently set to /tmp/savefile.gg by default. To save into a differently named file use mmsaveas.

''/tmp/testmodel.gg'' mmsaveas

Subsequent calls to mmsave will use the supplied file name after a successful save with mmsaveas.

Macros

Because there is a full programming language supporting the system, it is possible to create complex macros that do useful things. Here, for example, is a macro that will create a full 3d box (aligned to main axes) when given two points on opposite corners of the box. This will create a box with a corner at 0,0,0 and an opposite corner at 10,20,5. The entities comprising the box will be placed on the stack in a list.

0 0 0 p 10 20 5 p box -> [ <list_of_ents>]
Box Macro
''Function "Funbox"'' #
''Macro to create all the components of a box.'' #
''Includes 12 lines for edges, and 12 tris, 2 per side.'' #
|[->
P.x P.y Q.z p Q.x P.y P.z p dup2
P.x P.y P.z p t dup ''s1'' tag swap rot
P.x P.y P.z p t dup ''s1'' tag
P.x Q.y P.z p Q.x Q.y Q.z p dup2
P.x Q.y Q.z p t dup ''s2'' tag swap rot
Q.x Q.y P.z p t dup ''s2'' tag
P.x P.y Q.z p P.x Q.y P.z p dup2
P.x Q.y Q.z p t dup ''s3'' tag swap rot
P.x P.y P.z p t dup ''s3'' tag
Q.x P.y P.z p Q.x Q.y P.z p dup2
Q.x Q.y Q.z p t dup ''s4'' tag swap rot
Q.x P.y Q.z p t dup ''s4'' tag
Q.x P.y Q.z p P.x Q.y Q.z p dup2
P.x P.y Q.z p t dup ''s5'' tag swap rot
Q.x Q.y Q.z p t dup ''s5'' tag
P.x P.y P.z p Q.x Q.y P.z p dup2
Q.x P.y P.z p t dup ''s6'' tag swap rot
P.x Q.y P.z p  t dup ''s6'' tag
P.x P.y P.z p dup
Q.x P.y P.z p dup rot l dup ''e1'' tag swap rot swap
Q.x P.y Q.z p dup rot l dup ''e2'' tag swap rot swap
P.x P.y Q.z p dup rot l dup ''e3'' tag swap rot swap
l dup ''e4'' tag swap
P.x Q.y P.z p dup
Q.x Q.y P.z p dup rot l dup ''e5'' tag swap rot swap
Q.x Q.y Q.z p dup rot l dup ''e6'' tag swap rot swap
P.x Q.y Q.z p dup rot l dup ''e7'' tag swap rot swap
l dup ''e8'' tag swap
P.x P.y P.z p P.x Q.y P.z p l dup ''e9'' tag
Q.x P.y P.z p Q.x Q.y P.z p l dup ''e10'' tag
Q.x P.y Q.z p Q.x Q.y Q.z p l dup ''e11'' tag
P.x P.y Q.z p P.x Q.y Q.z p l dup ''e12'' tag
24 2list
::! P Q] |box sto

TODO

Query Entities - TODO

Find entity by coordinates. Enter a point and get a list of all entities which use this point. (hint EntityList.find_ents_by_coords())

What entities exist in some mathematical way at the provided point?

[10 10 0] esearch -> [ <entID> ... <entID> ]
[10 10 0] e? -> [ <entID> ... <entID> ]

What entities have a definition point matching the provided one?

[10 10 0] ep? -> [ <entID> ... <entID> ]
[10 10 0] epsearch -> [ <entID> ... <entID> ]

What entities have all definition points inside the provided bounding box?

[5 5 5] [12 12 12] ebbsearch -> [ <entID> ... <entID> ]
[5 5 5] [12 12 12] ebb? -> [ <entID> ... <entID> ]

What entities have some part inside the provided bounding box? This uses the 8< syntax which is like scissors representing a "cutting" bounding box.

[5 5 5] [12 12 12] ebbsearch8< -> [ <entID> ... <entID> ]
[5 5 5] [12 12 12] ebb?8< -> [ <entID> ... <entID> ]
  • What about spherical nearness to a point?

  • What about within a cylindrical boundary (useful for catching things near a line)? tube

  • What about entities that a specified line passes through? spear? laser? When the line passes through just the points proper?

Scale - TODO

Look at listwise *. Scale an entity or a point evenly. Can this kind of polymorphism work?

<entID> .5 scale -> <entID> +SIDE_EFFECT:entID is now 1/2 size
[8 10 12] .5 scale -> [4 5 6]
<entID> [1 .5 2] scale -> <entID> +SIDE_EFFECT:entID scaled xyz
[8 10 12] [1 .5 2] scale -> [8 5 24]
(3) <entID> OR [<entID1> ... <entIDn>]
(2) [Xbase Ybase Zbase] or ???
(1) UniformFactor OR [Xfactor Yfactor Zfactor]
gg--> scale

Can mirroring be done with scale? Should be doable for simple mirrors on axes. Probably the rest of it can be done with a fancy macro. Need to think about mirroring.

[8 10 12] [-1 1 1] scale -> [-8 5 24]

Unimplemented Attributes

~@ "No render" attribute to make the equivalent of hidden layers.

Highlight attribute. This is for just highlighting an entity. Perhaps this could even trigger 3 lines for highlighted tris too.

@@

There are some other SVG properties that are not implemented. For reference, here are some of them.

  • stroke-linecap

  • stroke-dashoffset

  • fill - simple color specification for areas (maybe not helpful here)

  • stroke-linejoin (not useful here yet)

See SVG docs for inspiration.

In the future arrow end points might be explored, but they are not implemented now. Maybe it’s not even possible.

A@end A@start A@both A@-> A@<- A@<->

Viewing - TODO

Non model output - TODO

It should be possible to turn on modes where geometry which is not part of the model is sent to the rendering pipeline.

Display a grid. Maybe there could be a grid spacing setting.

grid ->
VAL gridspacing ->

Draw a representation of the origin and +x, +y, and +z or the 3 axis in general.

xyzicon ->
axes ->

Simply puts an SVG point marker at this place for highlighting and reference only. The model is unaffected. Disappears on next render. Note that the input point is not dropped so that it’s easy to check if the point on the stack is the one you’re interested in.

[10 1.5 -3] refpt -> [10 1.5 -3]

Draw a 3d bounding box for reference.

[0 0 0] [10 10 10] refbb ->

Draw a reference bounding box of the model’s extents.

[0 0 0] [10 10 10] refextbb ->

Highlight entity. Take an entity or list of entities and give them a special highlighted rendering. Don’t drop them from the stack so this can be used to verify you’re working with the entities you intend to.

<entID> highlight -> <entID>
<entID> ; -> <entID>
[<entID1> ... <entIDn>] ; -> [<entID1> ... <entIDn>]
[<entID1> ... <entIDn>] highlight -> [<entID1> ... <entIDn>]

Variables that exist now - TODO

Set shallow angle cull value.

12 shallow ->

Set roll angle (to2d not ready).

30 roll ->

Set aspect (to2d not ready).

.75 aspect ->

Set zoom factor (to2d not read).

10 zoom ->

Reference For to2d

Options To to2d
to2d - Chris X Edwards
-a  Aspect ratio <ratio>. NOT DONE!
+c  Camera point <x,y,z>.
-d  Deleted lines visible <color>.
+f  Faces only, fill-in lines.
*h  This help message.
+o  Opacity flag. Hidden line removal.
-p  Pierce point marks <size>.
-r  Camera roll aka twist <angle>.
-s  Shallow surface cull <angle>.
+t  Target point <x,y,z>.
-z  Zoom factor <factor>. NOT DONE!

Bugs

  • [] produces [None]. Need to handle that much better. Contrast with tag? with a nonexistent tag.

  • Tons of input validation to do! There should not be catastrophic failure when there are not enough items on the stack.

  • Recursive naughtiness: RuntimeError: maximum recursion depth exceeded in cmp Or is that correct/reasonable behavior? Maybe at least trap it.

    [ dang 1 2 ] |dang sto dang wake ! -> ** RuntimeError **

To Do

  • ! Work on adding zoom and pan, especially zoom extents. Involves better calculations of vw.tr and vw.sc. Maybe assume SVG should be Y inverted too.

  • Create view angle commands azi (azimuth or bearing) and alt (altitude, 90-zenith) that can set vw.ca using angular coordinates. This would allow the user to orbit the model more predictably.

  • Change current simplistic mmsave to mmggout or something like that (maybe mmdump after SQL engines). Make a new and improved mmsave that really optimally saves the model. Needs to be efficient for saving/loading time and an saving space. Also should save session state properties (like vw) and anything else there might be. Does not need to be human readable. Need an mmload to go with this.

  • The ability to save arbitrary parts of a model would be good. Perhaps provide items, a file name, and itemsave. Then itemload to load in those entities.

  • Improve to2d.

  • Convert VAL to V, LST to L, TXT to T, SYM to S in output strings.

  • Limit huge items on stack output display (maybe toggle verbose setting to display everything or polite) (1) LST:[VAL:0.0, VAL:1.0, VAL:2.0, VAL:3.0, VAL:4.0, VAL:5.0, VAL:6.0, VAL:7.0, VAL:8.0, VAL:9.0, VAL:10.0, VAL:11.0, .......+359

  • Define StackOB_LST of argv.

  • Define functions to adjust settings (verbosity, special chars, etc.)

  • Better organized help - help by concept.

  • time, date, sleep

  • cd, ls, file_read, file_write, file_append

  • Suppress stack display for each operation during a source.

  • Polar move… Hmmm, what about the 3rd dimension?

  • Lining Tris. Often it is useful to have a tri entity with three matching line entities connecting its vertices. trilines? It might also be good to have a plausible process to compose a tri from 3 connected lines, i.e. 3 line entities which only reference 3 total points.

  • Macros for Arrays (grids, bolt circles, etc).

  • Macros for circles, arcs, discs, ellipses, cylinders, tubes, cones, etc. Start thinking of best 1st class entities for such things.

  • Macros for multi-segmented lines. Think about 1st class entities for such things. Also splines and friends.

  • Think about 1st class entity for Text (which is damn easy in SVG).

  • Think about 1st class entity for point markers.

  • Macros for holes and cutouts.

  • Extrusions? Along an arbitrary vector (Ahem ACAD).

  • Trim and extend? Don’t know how exactly to style those but they’re handy.

  • Intersection. Compute point where two lines intersect. Also for non intersecting lines, calculate intersection of the lines' projections (shadows). Once this is done, use it in conjunction with the angle function to work out a method to calculate angles between two lines which do not share an endpoint. Or something like that.

  • tee Point from 1 perpendicular to 2-3. [0 0 0] [8 8 0] [8 0 0] tee -> [4 4 0]

  • Area measurements? Plausible? Volume? [[0 0 0] [10 10 10] [-5 0 5]] area -> 25

  • What about groups of groups? As long as all sublists contains LST or VALs, should be ok to traverse recursively. Need to have a better (recursive) function for the filter and map commands that check things.

  • Listwise operation: +, -, *, /, --, ++, sin, cos, ceil, inv, not, or, and, sqrt, tan, pow, neg, abs, mod. Maybe more. Double check ++ and -- which are currently a bit odd.

  • + and * are good. Check other operators for correct behavior.

Questionable
[4 8] 4 :- -> [8]
4 [4 8 4] :- -> [8 4]
[4 8 4] 4 :- -> [4 8]
[4] [1 2 3] - -> [3 2 3]
[4 5 6] [0 3] - -> [4 2 6]
  • Vector math! Add, subtract, dot, cross, inverse, length, angles, normalize. Note if the namespaces are the same, preserve. Or maybe preserve even if present in only one term and the list lengths are the same.

  • Extrema. From the points supplied, return the point with the largest or smallest X, Y, or Z. Not even sure this is a good idea.

  • Get undo command working with fn_mm. This is important but hard. Actually most of it seems intact but each function needs to have its undo compliment worked out. Maybe an automated way to do that?

  • PostScript output. Should not be too difficult.

  • Or even more badass, review my ...prog/c/Pro2SD/output_gnuplot.cc

  • CGI. Everybody’s doing it. (clara.io)

  • WebGL.

  • TK, OpenGL.

Ideas From The HP48

List Operations

  • GETI - Does the same thing as get but returns the original list and the position value incremented by 1 before returning the value of the actual get operation. This allows it to be called repeatedly and all list members can be selected. A more modern way to do this might be ... map wake !

  • PUTI - Same dynamics as GETI. Not sure this is needed.

Stack

  • TYPE - Returns what kind of type an object is. (O.whatami)

  • →STR - Converts object to TXT object form.

  • STR→ - Evaluates text of string as if it were commands. Maybe wake should do this.

  • LAST - Places the same arguments of the most recently executed command on the stack as they were before the command was executed. This does not undo the last operation. Any changed objects will still be changed. Note that this conflicts with last which returns the last generated entity’s ID. That usage will probably stay.

Statistics

  • BINS - Sort into frequency bins.

  • UTPC - Upper Chi-Square Distribution

  • UTPF - Upper Snedecor’s F Distribution

  • UTPN - Upper Normal Distribution

  • UTPT - Upper Student’s t Distribution

Vector

  • CROSS

  • DOT

Time and Date

  • DATE

  • DATE+

  • DDATS

  • TIME

File

  • PATH

Run

  • INPUT

  • PROMPT

  • DBUG

  • SST - Single step.

  • NEXT

Math

  • SIGN

  • STO-, STO+, STO/, STO--, STO+ +, STO* - Note that the order shouldn’t matter as long as there is one sym and one value. v2 v1 sto* is equivalent to:

    SYM VAL STO* -> swap dup ! rot * swap sto
    VAL SYM STO* -> dup ! rot * swap sto

gglogo2