Home Building DHTML Scripting Using DiaperGlu Documention Key Script Commands Reference C Library API Reference Handy References About License Contact Forth Draft Standard
Running from console Entering script commands Using the data stack Using the string stack Compiling functions Using buffers Using LString arrays Using hierarchical lists Using the error stack Using files Using script files Using shared libraries Changing compile buffers Using pointers X86 Assembler Using structures Using Floating Point Using any size integers

Running DiaperGlu From the Console

If needed, change to the directory containing the compiled diaperglu executable. Initially this is the directory where you did nmake or sudo make.

On Mac OS X do ./diaperglu

On Windows do diaperglu

If there are no arguments on the command line, DiaperGlu will drop into the standard interactive command prompt mode. You can use the BYE command to exit DiaperGlu.

If you add any arguments on the command line, DiaperGlu will use the first argument as a path and filename to a DiaperGlu script. DiaperGlu will try to open the file, load it into a buffer, and Forth EVALUATE it. (This is called interpreting in some non Forth languages.) After the script file is interpreted, DiaperGlu will exit. Any other arguments are ignored.

Entering Script Commands

DiaperGlu treats everything separated by whitespace as a script command 'word', with a few exceptions.

That's it.

Need more of an explanation? O.K. Like Forth, DiaperGlu is simple. It starts at the beginning and parses until it finds a bunch of characters separated by 'delimiters', also known as whitespace. Then it treats this 'word' as a command, looks the word up in the current word list search order, and does the action associated with the word.

This means you have to leave whitespace between all of your script command words. No mushing them together like in other languages. For example, this is bad:

1 12+

The 1 is ok because there is a space after it. But DiaperGlu will not know what to make of the 12+ since the 12 and + are mushed together like a can of sardines. This is how the command line should look:

1 12 +

Now for the exceptions. Some command words, which are separated by whitespace, will look ahead and consume a bunch of characters, including whitespace. For example, the comment command ( will skip all characters up to and including the first ) it finds.

Using the Data Stack

DiaperGlu is Forth based and works like an HP calculator. Like Forth, most DiaperGlu script commands take and return parameters on the data stack. To put a number on the data stack, simply enter the number at the command prompt. Try this from a newly run instance of DiaperGlu:

5

To see the contents of the data stack, do this. There is a period before the S:

.S

Assuming the data stack was empty when you started, DiaperGlu should have printed out [ 5 ]. Lets put 2 more numbers on the stack and add them together. Try this. Remember to leave at least one space between each number and script command:

2 4 +

Now lets see what the stack looks like. Do this again. Don't forget the period before the S

.S

DiaperGlu should have displayed [5 6] indicating there is a 5 on the bottom of the stack, and a 6 on the top.

Using Hex Values

Unlike C and many other languages, the Forth standard did not allow using a 0x prefix or h suffix to indicate the base of a number at the time I wrote this part of DiaperGlu. Instead DiaperGlu, like Forth, is state based. You use script commands to set the number base. After the base is set, all numbers entered are interpreted as using that number base. Try this:

HEX 3C .S

The stack should show [5 6 3C]. Now try this:

DECIMAL .S

The stack should now show [5 6 60]. DiaperGlu starts off in decimal. If you use script files that change the base away from decimal I recommend adopting the habit of always setting the number base in your script files before entering any numbers. This also helps with maintenance when you add and remove code.

Other Number Bases

You can use other number bases with the BASE variable, even binary. Try this:

DECIMAL 2 BASE !

I set the radix to decimal before setting the base to make sure DiaperGlu would accept the 2. Now enter this binary value:

101001

Now set the base back to decimal and see what we have.

DECIMAL .S

DiaperGlu should now show [5 6 60 41]

Dropping values from the data stack

Now try this:

DROP .S

The stack should now show [5 6 60]. If you just want to see the top number on the stack use a period. This drops the top number on the stack and displays it as a signed number. Try this:

-1 .

DiaperGlu should have displayed -1. If you want to drop the top number and display it as an unsigned value, use the U. command. Try this. There is a period after the U:

HEX -1 U.

DiaperGlu should have shown FFFFFFFF.

Using the String Stack

Many DiaperGlu script commands take and return parameters on the string stack. If the string does not have a " in it, you can use the $" command to put it on the string stack. Try this. You need exactly one space after the $"

$" I'm a string."

Now let's see what the string stack looks like. Try this command. There is a period before the $S.:

.$S

DiaperGlu should have shown "I'm a string.". Note that the quotes are not part of the string. Let's put another one on. Try this. Don't forget the space after the $":

$" I'm on top!"

Let's see the stack again. Try .$S again:

.$S

DiaperGlu should now show "I'm a string." on the bottom followed by "I'm on top!" on top.

Dropping Strings Off the String Stack

To drop a string off the stack use DROP$. Try this:

DROP$ .$S

DiaperGlu should be back to one string and have just shown "I'm a string.". If you want to drop a string and display it at the same time, use the .$ command. Try this. There is a period before $:

.$

DiaperGlu should have shown "I'm a string." and the string stack should now be emtpy. Let's see. Try .$S again.

.$S

DiaperGlu should have told you the string stack is empty.

Strings with "

There are a couple ways to get strings with "s in them onto the string stack. One is to use the CHAR and C>$ commands. Try this. Don't forget the space after each $". CHAR pushes the code of the following character onto the data stack. C>$ pushes the character on top of the data stack onto the end of the string on top of the string stack. $>$ joins the top two strings on the stack together:

$" Moo" CHAR " C>$ $" Moo" $>$

Let's see what we have. Try this. There is a period before $S

.$S

DiaperGlu should have shown "Moo"Moo". Note that the quotes DiaperGlu shows before and after the string are not part of the string. Another method you can use relies on using the PARSE command with S>NEW$. PARSE returns the address and length of a string up to a character you choose. S>NEW$ takes the address and length of a string and pushes it onto the string stack. The parse end character can be any character that is not in the string. There is one thing to be careful of however, you have to use the address and length from PARSE before the end of the current input line or buffer. This is because DiaperGlu throws the string away at that point. So, all on one console line, enter this. Don't forget you need at least one space between each command and exactly one space after PARSE:

CHAR } PARSE string theory "rules" dude} S>NEW$

Let's see what is on the string stack. Try this:

.$S

DiaperGlu should have shown "moo"moo" on the bottom and "string theory "rules" dude" on top. Now, if you are compiling a subroutine using : you can't use PARSE this way. Instead, you have to drop out of compile mode using [ to do PARSE and use COMPILE-S to compile the string into the subroutine, drop back into compile mode using ], and then S>NEW$ to compile code to push it onto the string stack. Try this:

: MYSUB [ CHAR } PARSE one "two" three four} COMPILE-S ] S>NEW$ ;

This new string is not on the string stack yet because we haven't run the subroutine. Lets call the subroutine and then see what the string stack looks like. Try this:

MYSUB .$S

DiaperGlu should have shown "moo"moo" on the bottom, "string theory "rules" dude" in the middle, and "one "two" three four" on top.

Compiling Functions

The : yourfunctionname starts a function. The ; command ends the function. DiaperGlu compiles this function immediately into machine code, which is one of the reasons DiaperGlu is so fast. Try this:

: mytestfunction DUP 1+ .S ;

Then run the function. This function needs a number on the data stack to get started so do this:

0 mytestfunction

DiaperGlu should have shown 0 1. Run the function again:

mytestfunction

DiaperGlu should have shown 0 1 2. Now lets make a function to clear the data stack. Yes, there is a faster way. Try this:

: cleardatastack BEGIN DEPTH 0= 0= WHILE DROP REPEAT ;

Now run this new command. Try this:

cleardatastack .S

DiaperGlu should have reported an empty data stack.

Using Buffers

If you're going to use memory responsibly, you need to track your memory allocations and free them when you are done. C and operating system memory allocation routines do not do this automatically. On some operating systems, memory you hold is not freed even when your program exits, causing a leak. In addition, some operating systems require you to remember the exact address and length used during the allocation when you free them. Some even only allow allocations of sizes in multiples of something called the system page size.

All these things to keep track of. Shouldn't there be a standard method of dealing with all these issues, especially if you are trying to write code that is portable to multiple platforms?

DiaperGlu deals with all these issues with its buffer management system. Run diaperglu and try this from the command prompt:

1000 1000 NEWBUFFER

This means you are requesting a buffer that grows 1000 bytes at a time up to a maximum of 1000 bytes. Since the grow by size and maximum size are the same, DiaperGlu will allocate all the memory for this buffer at one time, and the address of this buffer will never change, but the amount DiaperGlu allocates may be more than 1000 bytes. Assuming your computer is not out of memory, DiaperGlu will round the 1000 bytes up to the nearest system page size, allocate memory from the operating system for you, then return a number identifying the new buffer. If DiaperGlu successfully allocated your buffer from the operating system with no errors, try this from the command prompt. There is a period before the S:

.S

DiaperGlu will show you the newly created buffer's identifier. This is the number you use to identify the buffer with the other buffer commands. If you would like to associate a name with this number so you do not have to remember the number, do this from the command line:

CONSTANT MYBUFFER

Now try this:

MYBUFFER LENGTHBUF

DiaperGlu returns the length of the buffer. Now, do this to see the length. There is a period after the U:

U.

If all is well, DiaperGlu should display '0'. Why is the length of the buffer 0 when you asked for 1000? DiaperGlu keeps track of how much of the buffer you are using separately from how much is allocated from the operating system. This way if you need a buffer of only 30 bytes but the operating system allocates in sizes of 1024 bytes, your script can deal with the buffer as if it is only 30 bytes long. To allocate 30 bytes of that buffer, do this:

30 MYBUFFER GROWBY

and try this again:

MYBUFFER LENGTHBUF U.

DiaperGlu should report 30. These 30 bytes are unitialized which means they could have any value. Now let's put something into this buffer. Try this:

1234 8 MYBUFFER PUTBUFFERUINT32

This puts 1234 into the buffer at an offset 8 bytes from the beginning. DiaperGlu uses 4 bytes to hold 1234. Now let's get a copy of this number back out of the buffer. Try this:

8 MYBUFFER GETBUFFERUINT32 U.

DiaperGlu should display 1234. Now try putting a byte off the end of the buffer. Do this:

21 30 MYBUFFER PUTBUFFERBYTE

DiaperGlu will not write off the end of the buffer and will report some errors. To see the errors, do this:

SHOW-ERRORS

DiaperGlu should show a list representing the error. To clear the error stack and move on, do this:

EMPTY-ERRORS

If you don't want to type the long commands, I have included shorter versions based on the Forth style:

  • O@ is short for GETBUFFERUINT64
  • O! is short for PUTBUFFERUINT64
  • OC@ is short for GETBUFFERBYTE
  • OC! is short for PUTBUFFERBYTE
Try this:

56789 10 MYBUFFER O!

Then this:

10 MYBUFFER O@ U.

DiaperGlu should have shown 56789. Now we are done with the buffer. To free the buffer and release it's memory back to the operating system do this:

MYBUFFER FREEBUFFER

If you forget, DiaperGlu will automatically free the buffer for you when exit DiaperGlu normally, either at the end of the top script file, or if you use BYE from the command line. The only time DiaperGlu will not free buffers automatically is if you force DiaperGlu to quit using some external means, such as CTL-C in a console.

Stretchy Buffers

What if you want a buffer that does not use a lot of memory from the operating system at first, but can get more memory from the operating system later if it is needed? DiaperGlu can do that too. Just use a slightly different NEWBUFFER command. Try this:

1000 10000 NEWBUFFER

Now DiaperGlu will initially allocate memory for a buffer from the operating system using the nearest multiple of the system page size greater or equal to 1000 bytes, but will allow this buffer to later grow to the nearest multiple of the system page size greater or equal to 10000 bytes. Let's assume the nearest system page size was 1024 bytes. If you grow your buffer to a length more than 1024 bytes, DiaperGlu will allocate more memory from the operating system in units of 1024 bytes up to a maximum size of 10240 bytes. If you try to go over this maximum size, DiaperGlu will not do it and return errors instead. This way you can have buffers that don't use up more memory than you need, but still put a limit on how much they can take. There is one thing to be careful of if you are using buffers that can grow, when they grow their base address will likely change. If you have any pointers into the buffer when it grows, they become invalid.

Only limited by available memory buffers

Just do this. DiaperGlu will use the largest multiple of the system page size possible as the maximum length of the buffer.

HEX 1000 FFFFFFFF NEWBUFFER

Notes on Buffers

At this time there is no way to make a buffer release unused memory back to the operating system other than freeing it with FREEBUFFER. Also, when a buffer gets more memory from the operating system to grow, it will likely be relocated to a different address in memory. This makes any pointers you have into the buffer invalid. In other words, if you get a pointer into the buffer, use it immediately. If you use GROWBUFFER or any of the words that push to the buffer, you should assume the pointer is no longer valid.

Using a buffer as a UINT64 stack

Try this:

1000 10000 NEWBUFFER CONSTANT mybuf

Then this:

12345 mybuf >BUF 6789 >BUF

Now the two numbers have been pushed onto the end of the buffer as UINT64s, taking up 8 bytes each. Now check out how long the buffer is in bytes:

mybuf LENGTHBUF U.

DiaperGlu should report 8. Ok, lets pop the first one off:

mybuf BUF> U.

DiaperGlu should have displayed 6789. Now lets get the last one off:

mybuf BUF> U.

DiaperGlu should have displayed 12345

x86 page handles

On Mac Os, DiaperGlu uses mmap to allocate memory for buffers. I have found out that mmap allocates pages which use handles that are stored on the x86 processor. There are a finite number of these page handles. So you could still have lots of memory, but if you run out of page handles, the Mac operating system will start swapping out pages to the file system, which really slows things down. After I found this out I also did some research into how the C malloc functions works, and if you allocate things of a size equal to or larger than one page (4k), it will also allocate a page using a page handle. In short, it's probably a good idea to keep the number of buffers you allocate in your program to a minimum. If you need memory for a lot of small things, you could probably use an lstring array. If you are going to be freeing them a lot, then a freeable lstring array might work. Some day I'll add something to make using freeable lstrings more efficient. (J.N. 4/13/2020). It's possible there are enough page handles to cover the memory in the computer at 4k per handle... If so, you might not need to worry about page handles.... I'll need to do more research. (J.N. 3/22/2023)

Using the Error Stack

This program comes with an error stack. Almost all paths through almost all of the words push errors to the error stack. If something goes wrong you will likely know about it instead of the program just crashing.

Some things checked in this program at run time that aren't checked or trapped in other languages are: array bound errors, buffer bound errors, stack underflows, memory access violations, and out of memory conditions.

Many times when an error occurs in one of DiaperGlu's routines, the subroutine pushes error strings to the error stack and immediately exits. Each error is pushed as the bufferid and offset of a C style null terminated string describing the error. After this, the parent routine often pushes the name of the buffer affected, and the parent routine's name to the error stack. Then each parent routine usually pushes it's name to the error stack as well. These are also C style null terminated strings. If left unhandled, when control returns to the main interpreter loop, the user prompt indicates the number of messages on the error stack along with instructions on how to see them. This lets you easily see what the initial problem was, and gives you a trace of the subroutines involved as well.

Let's see the error stack in action. From a DiaperGlu command prompt, try this:

-1 @

Since -1 is an invalid address on most computers, you should get some errors. Do this to see them:

SHOW-ERRORS

DiaperGlu should have shown a bad memory error first, representing the bottom; with the trace of the routines involved after. The message displayed last, representing the top of the error stack, should be the @.

The error stack is also protected. The program is designed so that it is difficult to access the error stack's memory except through the error access functions. If you try to use the buffer routines to access it, most of them will give you an error.

If you would like your subroutines to use the same error handling scheme as DiaperGlu, you can use these functions in your code:

  • NAME>E
  • ?ERRORIF
?ERRORIF compiles code to check to see if any new errors have occurred since the entering of the subroutine, and compiles an IF. NAME>E pushes the name of the last created word, which is usually the name of the current subroutine, to the error stack. Here is a sample routine. Note, : starts a subroutine, and ; ends it. Try this:

: myfunctionname DROP ?ERRORIF NAME>E THEN ;

If you call myfunctionname when the data stack is empty, myfunctionname shows up on top of the error stack as an error message, along with the other error messages. Let's try it. First, use .S and DROP to empty the error stack. Then do this:

myfunctionname

If your data stack was empty, DiaperGlu should have reported the errors. Do this to see them:

SHOW-ERRORS

Now let's empty the error stack and try again. Only this time, let's put something on the data stack so we don't get an error. Try this:

EMPTY-ERRORS 5 myfunctionname

DiaperGlu should not have given an error. One last thing, if you use this system, your functions should still work correctly even if there are errors on the error stack since this system only checks for new errors. However, once DiaperGlu gets back out to the command line or the main script, DiaperGlu will stop interpretting the line or script.

Running a DiaperGlu Script File

From a terminal or console command prompt do:

diaperglu scriptfilename

Including One DiaperGlu Script File in Another DiaperGlu Script File

In your script file, do this:

$" targetscriptfilename" INCLUDEFILE$

Sometimes you may want to include the same target script file in many other script files, which could cause it to be included more than once. One way to prevent this is to use ' to check to see if a word in is defined and exit compiling the target script if it isn't. ' returns ENDOFWORDLIST if the word is not defined. There is one thing that will foil this scheme. The wordlist containing the key word needs to be in the search order when your script checks. An easy way to make sure this happens is to always have the search order set to the default search order when including scripts, and use a new word wordlist that is in the default search order to hold the key word. When DiaperGlu starts, this is true. So assuming you haven't changed the search order or current new word wordlist, this will work:

' myscriptfilename.alreadyincluded ENDOFWORDLIST = 0= ?END-EVALUATE

0 CONSTANT myscriptfilename.alreadyincluded

Using Files

I wanted file I/O to be simple and fast so I made DiaperGlu work directly from buffers, not files. To use a file you first have to load it into a buffer. DiaperGlu will load the entire file at once into a buffer. DiaperGlu will not leave the file open. So to load a file do this:

$" targetfilename $" LOADFILE$

If everything goes ok, DiaperGlu will load the file into a buffer then return the buffer's identifier on the data stack. If something does not go ok, like the operating system could not find the file, or you do not have permission, DiaperGlu will return errors on the error stack.

Saving a file works the same way. You have to save an entire buffer to a file. If the file already exists, it is overwritten. Again DiaperGlu will not leave the file open. So to save a file do this:

bufferid $" targetfilename $" SAVEFILE$

If everything went ok, there is now a file saved whose contents exactly match the contents of buffer bufferid. The downside of this scheme is that DiaperGlu is not designed to directly work on really large files. But if you really need to work on a large file, you can probably find a way to do it through an external shared library, such as your operating system's kernel.

Using L$ Arrays

What if you want a stack or an array of variable length strings or structures but don't want to allocate a separate buffer for each string or structure? What if you know the system page size on some operating systems is rather large (Mac OS X = 4096 bytes) and realize that using a large block of bytes of operating system memory for each small structure or string is really wasteful? What if you know allocating memory from the operating system for each invidual structure or string is a lot slower than doing one allocation for a block of them? What if you want to be fast AND efficient?

Not a problem in DiaperGlu. Just use an L$ array. DiaperGlu L$ arrays pack all the string characters into one large buffer, and keeps track of the offsets in another. Yes this can be a problem if you want keep track of very large strings. Or if you want to keep track of a huge number of strings which you will constantly resize. But if you have a bunch of little strings, especially if you are building a string stack where you will often only resize the string on the top of the stack, an L$ array is really a fast and efficient way to go.

To get started, first you need to make two empty buffers to hold the L$ array, so do this:

1000 -1 NEWBUFFER CONSTANT myL$offsetbuf

1000 -1 NEWBUFFER CONSTANT myL$stringbuf

If your computer had enough memory to allocate the buffers, you are ready to move on. Next, to push an empty L$ onto the end of the new L$ array as if it were an L$ stack, do this:

myL$offsetbuf myL$stringbuf NEWL$

You now have one empty L$ in your L$ array. Let's push a string onto the end of the L$ array. We will need to get that string from somewhere so let's use the string stack. Remember you need a space between $" and H:

$" Hello " 0 GETS$[ND] myL$offsetbuf myL$stringbuf S>NEWL$

Let's see how long the top L$ is. The LENGTHL$[N] function only needs the L$ offset buffer id. Try this:

1 myL$offsetbuf LENGTHL$[N] U.

DiaperGlu should report 6, or 5 if you didn't put the space after the o. Now lets get a temporary pointer to and length of the last L$ and display it. Note, this pointer is only valid until the next time you do something that modifies the lengths of any of the L$s in the array, or you add or delete L$s from the array.

1 myL$offsetbuf myL$stringbuf GETSL$[N] TYPE

Tada!

Using Hierarchical Lists

Hierarchical lists in DiaperGlu are name value pairs where the names are organized into a parent child tree.

  • names and values are lstrings
  • children of a parent element are sorted by name, treating the name string as an unsigned byte array
  • children of a parent are also organized in order of when they were added to the parent

Making a new hierarchical list:

NEW-HLIST CONSTANT myhlist

Adding the root element. Use ENDOFLIST as the parent to specify an element with no parent:

$" myrootvalue$" $" myrootname$" ENDOFLIST myhlist NEW-ELEMENT CONSTANT myrootelement

Adding the first child element of the root:

$" mychildvalue$" $" mychildname$" myrootelement myhlist NEW-ELEMENT CONSTANT mychildelement

Now you can add more children. When you are all done all you have to do is free the whole hlist to release all the memory:

myhlist FREE-HLIST

Or, if you are feeling lazy, you can leave it, and DiaperGlu will release the memory when you do BYE.

Using Functions in a Shared Object Library

This program can dynamically link to almost any library in your system which means all the functions in the .dll, .so, or .dylib files on your computer are essentially part of this program.

Loading shared object library files

To load a shared object library file and extract its exported functions and symbols into a new word list, do this:

$" dllname.dll" LOADLIBRARY$

This returns the word list id of the new word list holding the shared object library's symbols on the data stack. To save this word list id for later use I recommend doing this:

CONSTANT dllname-WORDLIST

To see the loaded symbols, do this:

dllname-WORDLIST SHOW-WORDLIST

In order for the interpreter to be able to find the words in this word list, the word list needs to be added to the search order. To add this word list to the search order do this:

dllname-WORDLIST >SEARCH-ORDER

If different shared libraries have the same symbol name in them, it is not a problem in DiaperGlu. Just make sure the word list with the desired target symbol is closer to the top of the search order. The last word list pushed onto the search order is on top.

Do not do this now. Later, to drop the top word list from the search order, do this:

SEARCH-ORDER>

Calling shared object library functions

To call a CDECL, STDCALL, or C++ member function (except C++ member functions on win32), that return either void or something of size UINT64 or smaller (this includes double floats), do this:
  • make sure the word list containing the function's symbol is in the search order
  • push the parameters in reverse order onto the data stack if they are size UINT64 or smaller. If the parameter is a UINT128, SWAP the UINT64 halves first before pushing both halves to the data stack.
  • if compiling a call to a C++ member function, push the object handle onto the data stack
  • push the number of parameters to the data stack. If you are pushing a UINT128, count it as two parameters. If you are pushing a C++ member function object handle count it as a parameter
  • use the symbol name as a script command
  • either use or drop the returned UINT64 (void subroutines return a fake UINT64)
To call a CDECL, STDCALL, or C++ member function, that return something of size UINT128, outside of a subroutine do this:
  • make sure the word list containing the function's symbol is in the search order
  • push the parameters in reverse order onto the data stack if they are size UINT64 or smaller. If the parameter is a UINT128, SWAP the UINT64 halves first before pushing both halves to the data stack.
  • if compiling a call to a C++ member function, push the object handle onto the data stack
  • push the number of parameters to the data stack. If you are pushing a UINT128, count it as two parameters. If you are pushing a C++ member function object handle, count it as a parameter.
  • do ' functionssymbol CALLCDECLRETUINT64
  • either use or drop the returned UINT64
To compile a call to a CDECL, STDCALL, or C++ member function, that return something of size UINT64, inside of a subroutine do this:
  • make sure the word list containing the function's symbol is in the search order
  • push the parameters in reverse order onto the data stack if they are size UINT64 or smaller. If the parameter is a UINT128, SWAP the UINT64 halves first before pushing both halves to the data stack.
  • if compiling a call to a C++ member function, push the object handle onto the data stack
  • push the number of parameters to the data stack. If you are pushing a UINT128, count it as two parameters. If you are pushing a C++ member function object handle count it as a parameter.
  • then add this to your subroutine: ['] functionssymbol CALLCDECLRETUINT128
  • finally, either use or drop the returned UINT128

Figuring out how to pass certain data types

When calling a 64 bit shared object library, all parameters are usually passed as 64 bit entities. An int gets sign extended to 64 bits. BOOL and CHAR get padded to 64 bits. And all pointers to structures, ints, chars, and bools are 64 bits. And since the data stack is 64 bits you will need to push one number to the data stack for every parameter.

If you need to pass a pointer to a structure to a shared object library function, some options are:

  • If the shared object library has it, use it's function to allocate memory and return a pointer to the structure. If you do this, you need to remember to call the shared object library's corresponding free function or you will leak memory.
  • Use CREATE and ALLOT. If you do, the name of the definition pushes a pointer to the structure onto the data stack.
  • Use NEWBUFFER GROWBY and GETPBUFFERSEGMENT, don't forget to free the buffer when you are done. Or better yet, just allocate one buffer for all your shared object library calls and use it as a structure stack.
  • Use an L$ stack to hold your structures.

If you need to pass a pointer to a character array, you can use $" with GETP$. Don't forget to do DROP$ when you are done when you are done with the pointer. Also don't forget to push all your strings to the string stack before getting the pointers, and to use the pointers before pushing any more strings. This is because the string stack may grow when you push a string to it, possibly relocating it's base address in memory. If the string stack grows, any pointers into the string stack become invalid.

If you need to pass a pointer to a C style 0 string, you can use $" and $>0$ with GETP$. Don't forget to do DROP$ when you are done. A C style 0 string is an array of characters with the trailing byte set to 0.

If you need to pass in a char* argv[] type array, this is an array of pointers to 0 strings. So you need to create the 0 strings, then create an array of pointers to the 0 strings, and pass in a pointer to the array.

If you need to pass a UINT128, push it to the data stack then do a swap. You need to do this swap because the function caller code reverses the parameters in UINT64 size chunks when it moves them to the return stack, and this reverses the halves of the UINT128.

Changing the buffers where code is compiled, where variables are allocated, and new word headers are stored

When a DiaperGlu process starts, new word headers go into separate memory from new code and new variables; but the memory for new variables and new code is shared.

New word headers go into an array held in a buffer so DiaperGlu can meet the Forth Standard's requirement for 'one cell' execution tokens. The new word header's index in this array is it's the execution token. You can't change this.

According to the Forth standard, CREATE ALLOT C, , and VARIABLE operate on the data space. This program follows the Forth standard.

>According to the Forth standard, : operates on the code space. This program follows the Forth standard.

PCURRENTCOMPILEBUFFER points to the current code space which is the buffer where new compiled code will go. When DiaperGlu starts, this is the DATASPACEBUFFERID.

PCURRENTNEWVARIABLEBUFFER points to the current data space, which is the buffer where new variables will go. When DiaperGlu starts, this is the DATASPACEBUFFERID.

So, this means if you want to cross compile code for another target or make an assembler for this target that compiles in a new buffer, you can't really use the Forth standard words like C, and , to do it. You can however make a new buffer and then use words like BUFC> instead.

It also means if you change the current compile buffer to a buffer that can move, you need to be very careful if you define any compiling words, and then use those words to compile to the same buffer. This is because the buffer could move while your compiling word is compiling, and then when the processor tries to return from the compiling, the buffer is gone. Big problem. Fortunately I have included the word SAFE for just such emergencies. Simply use this word immediately after defining your subroutine to change the compile type of your compiling word to safe and your processor will never get lost. You also need to make any words that call your compiling words safe as well, since they will also be, ahem, compiling words :-) For example:

HEX : mycompileRTS C3 PCURRENTCOMPILEBUFFER @ C>BUF ; SAFE

Using Pointers

Be careful with pointers into buffers. If the buffer moves the pointer becomes invalid.

To avoid nasty problems I recommend you allocate all your memory first. Then after you get any pointers, use them before you do anything that allocates any more memory.

Or you can lock down your buffers so they don't move by making the growby equal to the maxsize when you create them.

Some of the things that may allocate memory are: compiling, pushing to buffers, pushing to a string stack, or declaring variables.

Note:

When the process first starts in console mode, the current new variable buffer and current compile buffer point to the same buffer, DATASPACEBUFFERID, which is locked down. This is so old FORTH programs don't have to worry about how they used variables. But if you change them then you have to worry.

The data stack, rstack, terminal input buffer, pad, error stack, word buffer, leave stack, search order stack, and word list header array are locked down. Pointers into them will always be valid unless you change the source code in initbuffers() or increase their maxsize at run time.

The string stack is not locked down and can move. If you need to move a string on the string stack to the top of the string stack, use PICK$. Do NOT use GETP$ with S>NEW$.

This also goes for any new buffers you make which are not locked down. Do NOT push to the buffer using a pointer into the buffer.

For example:

1000 1000000 NEWBUFFER CONSTANT MYVARIABLEBUFFER

MYVARIABLEBUFFER CURRENTNEWVARIABLEBUFFER !

VARIABLE PMYVAR ( allocates 4 bytes in the current new variable buffer )

PMYVAR ( calculates and pushes the address of the variable based on its offset and buffer id )

1000 ALLOT ( your variable buffer has probably moved making the pointer on the data stack invalid! )

DROP ( throwing away the bad pointer)

PMYVAR ( calculating the new address which will be valid )

Example 2 - don't do this:

1000 1000000 NEWBUFFER CONSTANT MYBUF2

999 MYBUF2 GROWBUFFER

10 MYBUF2 5 GETPBUFFERSEGMENT ( get pointer to 10th byte in MYBUF2 )

1 PUSHBUFFERSEGMENT ( try to push one byte to MYBUF2, but S>BUF will grow the buffer BEFORE getting the byte from the source making the pointer invalid! )

The thing that's really bad about this scenario is you may not get a bad address error because even though the memory went back to the OS, the OS may consider it to still be in your process space. If some other stuff happened between the time you got the pointer and did S>BUF, the freed memory could have been overwritten and the wrong number could have been pushed. This is one of those very difficult to debug situations where most of the time it will work, but once in awhile it won't.

Using Floating Point

This program includes the Forth '94 draft standard floating point words and floating point extension words. The main things you need to know are that this program uses a separate double precision (64 bit) floating point stack, and in order to push values to the floating point stack the BASE has to be DECIMAL and the floating point number word has to have an d D e or E in it. The d D e E represents 'times ten raised to an exponent'. You do not need to have a decimal point. The exponent, if you include one, has to be an integer.

Some examples: 0E means 0.0, 12.3E means 12.3, 12.3E0 also means 12.3, 12.3E-1 means 1.23. I have included some floating point constants like e and PI. If you put lower case e by itself you will not get 0.0, you will get the floating point constant e if the buffer wordlist is in the search order. If you use d D or E by itself you will get 0.0

The floating point words are documented in forthfloatingpoint.htm

Using Structures

This program includes words to help in declaring and using structures. The key word for this is [>ORDER]CONSTANT and it allows you to make namespaces like a structure. There is an example in the documenation of [>ORDER]CONSTANT that shows you one way to use it. Some other words that can help you make structures are CONSTANTS< and SIZED-CONSTANTS<

Using Integers of Arbitrary Size

This program has words to support using unsigned integers of arbitrary size. The integers are held on an lstring stack, lstring array, or on the string stack. These words are documented in the lstring and string stack pages.

Some info for Forth people

This compiles subroutine threaded code.

The data stack is 64 bits.

The data stack is used for the control flow stack.

The rstack is separate from the return stack. >R goes to the rstack.

 

For non Forth people

Sorry about using caps for all the commands, the Forth standard requires it. You dont have to use caps for the commands you define if you don't want to.

@ is short for get UINT64. ! is short for put UINT64. C@ is short for get byte. C! is short for put byte. >something is usually short for push to something or convert to something. Something> is usually short for pop from something. O is short for offset bufferid. I'm thinking about changing O to OB. BUF is short for buffer. I'm thinking about changing BUF to B. $ is short for string or string stack. S is short for address length. P is short for address.

Most commands work the same way compiled into a new command as if you typed them from the command line. The most notable exceptions are all the control flow commands like IF ELSE and THEN which won't do a branch if not compiled into a command. Some other ones which operate on the current input buffer actually do work the same way compiled into a command as in execute mode but don't seem to like PARSE and PARSE-WORD.

If you want to look like a really cool Forth programmer, put on a pair of dark shades and refer to the commands as "words" :-)

Just a note about modifying DiaperGlu. I just found out (v5.8) nmake may have trouble if the line endings of the source files are not consistent or not standard, like if you end them with lf maybe? I had an issue where it wasn't reading one of the files correctly until I pulled it into Visual Studio and made all the line endings the standard Windows line endings. Mac's TextEdit was also having trouble with the same file. So I do edit the source files using tools on both platforms and the line endings are not consistent in most of the files at this time. If you are seeing weird issues with nmake or TextEdit where it's crashing or reporting errors with line numbers that don't make any sense, it may be this issue.

Another v5.8 issue: I just found out about Mac's new Gatekeeper security program. It won't allow things to run on a computer unless it came from the App store, it was 'notarized' by Apple, or unless you built it from source on your computer. I can't afford the App store or notarization at this time. Windows may also have similar issues. For these reasons I am no longer distribruting executables in the release and you will have to build DiaperGlu from source.