https://rpg.hamsterrepublic.com/ohrrpgce/api.php?action=feedcontributions&user=Sorlok+reaves&feedformat=atom
OHRRPGCE-Wiki - User contributions [en]
2024-03-29T11:11:34Z
User contributions
MediaWiki 1.39.7
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=29019
Talk:Compiling for Android
2013-06-25T01:50:07Z
<p>Sorlok reaves: /* IT WORKS!!! */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That definitely should be possible! However I see lots of little changes are needed. Have you got a Raspberry Pi? I'd love to see it happen.<br />
<br />
AFAIK android uses identical ABIs to other ELF unix OSes on the same CPU architectures (though I get the hint that there may be slight differences in the linker due to some platforms not following the standards to the letter).<br />
<br />
My changes to both FB and the OHR conflate Android and ARM in several places. You would want to compile both for a GNU/Linux target instead of Android (if you run a normal GNU/Linux distrib with ncurses and X11 on the RPi). SConscript needs several changes in addition to the ones I just made. <s>If SDL on the RPi uses the frame buffer, then you may not want to link with the X11 libs</s> (actually libfb still links to X11 by default, unless you add -DDISABLE_X11 to config.mk). And it'll need to know where to find the FB libs (it looks like the Raspberry Pi toolchain uses a platform triple of arm-bcm2708hardfp-linux-gnueabi, so you'll probably edit SConscript where I hardcoded arm-linux-androideabi). Looking through my changes to FB, it looks like the only thing which needs changing for GNU/Linux on ARM is to edit rtlib/static/fbrt0.c to use __attribute__((constructor)). fbc should actually be fine for cross compiling to any 32 bit target as long as it's invoked with "-arch arm" (well not quite true: it assumes that it knows how all structures are laid out in memory (that they are the same as laid out by GCC on x86... I'm surprised this hasn't blown up on ARM!)<br />
<br />
Look at compile-using-toolchain.sh for how to invoke scons (you'd build Game/Custom directly using scons). You'd probably also need to add the directory containing a build of libSDL.so (and libSDL_mixer.so (or use music=silence)) cross-compiled to the Raspberry Pi to the CXXLINKFLAGS variable in SConscript (we ought to set the standard LDFLAGS variable instead).<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've got one coming in the mail ---and I noticed you separated the Android/ARM targets in a recent commit, thanks! I'll give it a whirl once my device arrives.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool. I might get around to some other clean up before then. Those SConscript changes were just one thing needed.<br />
<br />
<br />
==Hacking my way through the RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): So I've started working on cross-compiling for Raspberry PI. I'm stuck on the "make rtlib" step of FreeBasic (I'll work through it more later, but I figured I've reached a stopping point for tonight so might as well document it.)<br />
<br />
First, I installed crosstools-ng, which is by far the most common suggestion for setting up a toolchain. I followed this post for the most part:<br />
http://www.kitware.com/blog/home/post/426<br />
<br />
Then, I ssh-ed into the RPi and installed libSDL1.2-dev, and all related libraries. Then I did this:<br />
cd ~/opt/rpi_sync<br />
rsync -rl pi@192.168.1.110:/usr .<br />
<br />
Following that, I checked out the FreeBasic repo listed above and used this config.mk file:<br />
ENABLE_PREFIX=1<br />
prefix=/home/sethhetu/opt/fbc-0.90-rpi<br />
PATH:=/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/bin:$(PATH)<br />
CFLAGS := -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf<br />
TARGET_PREFIX=arm-unknown-linux-gnueabi-<br />
V=1<br />
<br />
Note that CFLAGS; I was guessing that this would allow me to use the same headers on both the RPi and my laptop. This is just a guess!<br />
<br />
At that point, I ran:<br />
make compiler<br />
<br />
It ran to completion, then:<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
<br />
It fails with:<br />
arm-unknown-linux-gnueabi-gcc -Wall -Werror-implicit-function-declaration -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf -c src/rtlib/linux/sys_portio.c -o src/rtlib/arm-unknown-linux-gnueabi-obj/sys_portio.o<br />
src/rtlib/linux/sys_portio.c: In function 'fb_hIn':<br />
src/rtlib/linux/sys_portio.c:11:2: error: impossible constraint in 'asm'<br />
<br />
The line in question:<br />
__asm__ volatile ("inb %1, %0" : "=a" (value) : "d" (port));<br />
<br />
Here's where I'm stumped. If it was a header-file error, I wouldn't be surprised. But it's a c file, and involves the correct toolchain item (arm-unknown-linux-gnueabi-gcc). And the same FreeBasic repo compiled fine for Android with a similar config.mk (but no CFLAGS in this case).<br />
<br />
Anyway, I'll debug more this weekend. Any suggestions would be appreciated.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool.<br />
<br />
I hadn't noticed that there was asm in use there. To do things properly, we'll want to move that stuff into an x86-specific file, and also provide an ARM implementation of port IO. I assume that anyone using a RPi for hardware hacking and using FB will want ARM implementations of that. I don't know anything about that stuff. But it apears that ARM doesn't have separate IO instructions. Someone said:<br />
The ARM architecture doesn't specify a separate I/O space -- everything is memory-mapped, so:<br />
1) There are no dedicated (fast-path) I/O instructions -- load and store is all you get.<br />
For example see [http://elinux.org/RPi_Low-level_peripherals#C_2 here] for an example of accessing the RPi GPIO pins using memory mapping. Well, I'll ask on the FB forums, there are people there who care about that.<br />
<br />
Anyway, just comment out that assembly. If you look at rtlib/android/sys_portio.c you'll see the functions throw errors.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just adding this brief tidbit; the Android-compiled version has:<br />
<br />
arm-linux-androideabi-gcc -DENABLE_MT -Wall -Werror-implicit-function-declaration -DDISABLE_FFI -DDISABLE_X11 -DDISABLE_NCURSES -Wfatal-errors -O2 -c src/rtlib/android/sys_portio.c -o src/rtlib/arm-linux-androideabi-objmt/sys_portio.o<br />
<br />
...which contains:<br />
<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
...etc. I'm sure you (TMC) know about this; I'm just adding it for clarity's sake to anyone else reading along.<br />
<br />
No time to work on this today, but I'll probably use FB_RTERROR_ILLEGALFUNCTIONCALL in the linux/sys_portio.c when I get a chance.<br />
<br />
'''Edit:''' Whoops, I should really read the Talk page before posting; the previous post by TMC already has all of this. Thanks for the link on I/O mapping; it's informative!<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, using FB_RTERROR... I can get Free Basic to compile and install. Now I'm on to compiling GAME. One problem that keeps coming up is an error similar to the following:<br />
<br />
/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/lib/gcc/arm-unknown-linux-gnueabi/4.7.3/../../../../arm-unknown-linux-gnueabi/bin/ld: warning: libpulse-simple.so.0, needed by /home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf/libSDL.so, not found (try using -rpath or -rpath-link)<br />
<br />
This is annoying, because I have libpulse-simple.so.0, and I have "-L/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf" in my C/CXXFLAGS. I tried adding this to SConscript:<br />
<br />
LDFLAGS = '-rpath-link="/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf"'<br />
<br />
..but it doesn't seem to help. Right now, I am simply adding every library manually to, e.g., SDL's list:<br />
<br />
'sdl':<br />
{'shared_modules': 'music_sdl.bas sdl_lumprwops.bas',<br />
'libraries': 'SDL SDL_mixer asound pulse-simple pulse directfb fusion direct caca ts mikmod vorbisfile vorbis FLAC'}<br />
<br />
Any ideas on getting this working would be very helpful.<br />
<br />
'''Edit:''' But don't think too hard; I'm probably just going to chroot my way around this problem regardless<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Did you add "-L/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf" to C/CXXFLAGS, or to CXXLINKFLAGS? You should add it to the later (in SConscript), that's what's actually used. And that really ought to be changed to LDFLAGS instead. (SConscript's philosophy is to ignore the environment that scons is invoked in in order to not depend on it, so generally our build script ignores env variables too.)<br />
<br />
Other than that, I actually don't really know much about all of that, and just fiddle with things until they work.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've tried each of those, and it doesn't help (thanks though). I'm in the middle of preparing a chroot for this (it's unrelated; I'm getting hard-includes to "/lib" and "/usr" as top-levels, so I figure a chroot is the only way to go). I'll post back with more results later.<br />
<br />
<br />
==First Successful Build with RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Don't get too excited; graphics don't work. But here's how I got it to compile.<br />
<br />
First, make a directory like so:<br />
mkdir ~/rpi_sync<br />
export RPI_HOME=/home/YOU/rpi_sync<br />
<br />
We'll do everything in this directory, so it should be relatively sane. <br />
<br />
Now, connect to your Pi and yank some stuff. The IP address will change, of course.<br />
cd $RPI_HOME<br />
rsync -rl pi@192.168.1.5:/usr .<br />
rsync -rl pi@192.168.1.5:/lib .<br />
<br />
So now "lib" and "usr" are copies of the Pi's. We need two more directories; one for our source and one for installed cross-compiling tools (fbc and gcc).<br />
cd $RPI_HOME<br />
mkdir source<br />
mkdir opt<br />
<br />
Let's make our compiler. We are using crosstool-ng, since it's the most common option. Follow this page:<br />
http://www.kitware.com/blog/home/post/426<br />
...except for the "prefix directory" choose:<br />
${RPI_HOME}/opt/${CT_TARGET}.<br />
<br />
In order to make the setting of environment variables slightly less painful, I've made a simple "wish-list", called "set_vars.source". You should copy/save this file to your $RPI_HOME directory:<br />
http://pastie.org/8076443<br />
<br />
From now on, when you open a terminal for compiling, do the following:<br />
cd $RPI_HOME<br />
source set_vars.source<br />
<br />
Now we need FreeBasic. Get the source:<br />
cd $RPI_HOME/source<br />
git clone git://git.code.sf.net/u/teeemcee/fbc freebasic<br />
<br />
You'll need to mangle things a bit. Open "freebasic/src/rtlib/linux/sys_portio.c", and change it to look like this:<br />
/* ports I/O functions */<br />
#include "../fb.h"<br />
#include "../unix/fb_private_console.h"<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
int fb_hOut( unsigned short port, unsigned char value ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
Now, create a file called "config.mk" in the "freebasic" directory and give it the following contents:<br />
ENABLE_PREFIX=1<br />
prefix=${RPI_HOME}/opt/fbc-0.90-android<br />
V=1<br />
<br />
Note: I know "android" is wrong here, but it's just a visual error, and I don't want to go scouring my ENV.<br />
<br />
Now, compile and install Freebasic as explained in the main page:<br />
make compiler<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
make install-compiler install-rtlib install-includes<br />
<br />
We're ready to compile the OHR's source. Grab it:<br />
cd $RPI_HOME/source<br />
svn co svn://gilgamesh.hamsterrepublic.com/ohrrpgce<br />
<br />
We need to change a few things. Open "ohrrpgce/wip/SConscript" and replace its text with the following:<br />
http://pastie.org/8076462<br />
<br />
Note: These changes are not the most elegant, and I would not recommend merging them. Essentially, they are all of the "arm+linux instead of arm+android" variety.<br />
<br />
There's one more thing you need to do, and it's ugly:<br />
sudo mkdir /lib/arm-linux-gnueabihf<br />
sudo ln -s ${RPI_HOME}/lib/arm-linux-gnueabihf/ld-linux-armhf.so.3 /lib/arm-linux-gnueabihf<br />
sudo ln -s ${RPI_HOME}/lib/arm-linux-gnueabihf/libc.so.6 /lib/arm-linux-gnueabihf<br />
sudo ln -s ${RPI_HOME}/lib/arm-linux-gnueabihf/libpthread.so.0 /lib/arm-linux-gnueabihf<br />
sudo mkdir /usr/lib/arm-linux-gnueabihf<br />
sudo ln -s ${RPI_HOME}/usr/lib/arm-linux-gnueabihf/libc_nonshared.a /usr/lib/arm-linux-gnueabihf<br />
sudo ln -s ${RPI_HOME}/usr/lib/arm-linux-gnueabihf/libpthread_nonshared.a /usr/lib/arm-linux-gnueabihf<br />
<br />
This probably won't mess up your system, since your compiler will never look in "arm-linux-gnueabihf" for libraries. Anyway, I don't know what's considered the "right" way to handle this.<br />
<br />
Now, build it:<br />
cd $RPI_HOME/source/ohrrpgce/wip<br />
scons fbc=$FBC rpi=1 debug=1 game<br />
<br />
Et voila!<br />
<br />
Of course it doesn't actually work; here's an strace:<br />
http://pastebin.com/bp8nND16<br />
<br />
...and an latrace:<br />
http://pastebin.com/N1GjRt0E<br />
<br />
More debugging to follow.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): If you use this Sconscript, it'll work:<br />
http://pastie.org/8077124</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=29018
Talk:Compiling for Android
2013-06-24T22:47:01Z
<p>Sorlok reaves: /* Memo */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That definitely should be possible! However I see lots of little changes are needed. Have you got a Raspberry Pi? I'd love to see it happen.<br />
<br />
AFAIK android uses identical ABIs to other ELF unix OSes on the same CPU architectures (though I get the hint that there may be slight differences in the linker due to some platforms not following the standards to the letter).<br />
<br />
My changes to both FB and the OHR conflate Android and ARM in several places. You would want to compile both for a GNU/Linux target instead of Android (if you run a normal GNU/Linux distrib with ncurses and X11 on the RPi). SConscript needs several changes in addition to the ones I just made. <s>If SDL on the RPi uses the frame buffer, then you may not want to link with the X11 libs</s> (actually libfb still links to X11 by default, unless you add -DDISABLE_X11 to config.mk). And it'll need to know where to find the FB libs (it looks like the Raspberry Pi toolchain uses a platform triple of arm-bcm2708hardfp-linux-gnueabi, so you'll probably edit SConscript where I hardcoded arm-linux-androideabi). Looking through my changes to FB, it looks like the only thing which needs changing for GNU/Linux on ARM is to edit rtlib/static/fbrt0.c to use __attribute__((constructor)). fbc should actually be fine for cross compiling to any 32 bit target as long as it's invoked with "-arch arm" (well not quite true: it assumes that it knows how all structures are laid out in memory (that they are the same as laid out by GCC on x86... I'm surprised this hasn't blown up on ARM!)<br />
<br />
Look at compile-using-toolchain.sh for how to invoke scons (you'd build Game/Custom directly using scons). You'd probably also need to add the directory containing a build of libSDL.so (and libSDL_mixer.so (or use music=silence)) cross-compiled to the Raspberry Pi to the CXXLINKFLAGS variable in SConscript (we ought to set the standard LDFLAGS variable instead).<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've got one coming in the mail ---and I noticed you separated the Android/ARM targets in a recent commit, thanks! I'll give it a whirl once my device arrives.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool. I might get around to some other clean up before then. Those SConscript changes were just one thing needed.<br />
<br />
<br />
==Hacking my way through the RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): So I've started working on cross-compiling for Raspberry PI. I'm stuck on the "make rtlib" step of FreeBasic (I'll work through it more later, but I figured I've reached a stopping point for tonight so might as well document it.)<br />
<br />
First, I installed crosstools-ng, which is by far the most common suggestion for setting up a toolchain. I followed this post for the most part:<br />
http://www.kitware.com/blog/home/post/426<br />
<br />
Then, I ssh-ed into the RPi and installed libSDL1.2-dev, and all related libraries. Then I did this:<br />
cd ~/opt/rpi_sync<br />
rsync -rl pi@192.168.1.110:/usr .<br />
<br />
Following that, I checked out the FreeBasic repo listed above and used this config.mk file:<br />
ENABLE_PREFIX=1<br />
prefix=/home/sethhetu/opt/fbc-0.90-rpi<br />
PATH:=/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/bin:$(PATH)<br />
CFLAGS := -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf<br />
TARGET_PREFIX=arm-unknown-linux-gnueabi-<br />
V=1<br />
<br />
Note that CFLAGS; I was guessing that this would allow me to use the same headers on both the RPi and my laptop. This is just a guess!<br />
<br />
At that point, I ran:<br />
make compiler<br />
<br />
It ran to completion, then:<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
<br />
It fails with:<br />
arm-unknown-linux-gnueabi-gcc -Wall -Werror-implicit-function-declaration -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf -c src/rtlib/linux/sys_portio.c -o src/rtlib/arm-unknown-linux-gnueabi-obj/sys_portio.o<br />
src/rtlib/linux/sys_portio.c: In function 'fb_hIn':<br />
src/rtlib/linux/sys_portio.c:11:2: error: impossible constraint in 'asm'<br />
<br />
The line in question:<br />
__asm__ volatile ("inb %1, %0" : "=a" (value) : "d" (port));<br />
<br />
Here's where I'm stumped. If it was a header-file error, I wouldn't be surprised. But it's a c file, and involves the correct toolchain item (arm-unknown-linux-gnueabi-gcc). And the same FreeBasic repo compiled fine for Android with a similar config.mk (but no CFLAGS in this case).<br />
<br />
Anyway, I'll debug more this weekend. Any suggestions would be appreciated.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool.<br />
<br />
I hadn't noticed that there was asm in use there. To do things properly, we'll want to move that stuff into an x86-specific file, and also provide an ARM implementation of port IO. I assume that anyone using a RPi for hardware hacking and using FB will want ARM implementations of that. I don't know anything about that stuff. But it apears that ARM doesn't have separate IO instructions. Someone said:<br />
The ARM architecture doesn't specify a separate I/O space -- everything is memory-mapped, so:<br />
1) There are no dedicated (fast-path) I/O instructions -- load and store is all you get.<br />
For example see [http://elinux.org/RPi_Low-level_peripherals#C_2 here] for an example of accessing the RPi GPIO pins using memory mapping. Well, I'll ask on the FB forums, there are people there who care about that.<br />
<br />
Anyway, just comment out that assembly. If you look at rtlib/android/sys_portio.c you'll see the functions throw errors.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just adding this brief tidbit; the Android-compiled version has:<br />
<br />
arm-linux-androideabi-gcc -DENABLE_MT -Wall -Werror-implicit-function-declaration -DDISABLE_FFI -DDISABLE_X11 -DDISABLE_NCURSES -Wfatal-errors -O2 -c src/rtlib/android/sys_portio.c -o src/rtlib/arm-linux-androideabi-objmt/sys_portio.o<br />
<br />
...which contains:<br />
<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
...etc. I'm sure you (TMC) know about this; I'm just adding it for clarity's sake to anyone else reading along.<br />
<br />
No time to work on this today, but I'll probably use FB_RTERROR_ILLEGALFUNCTIONCALL in the linux/sys_portio.c when I get a chance.<br />
<br />
'''Edit:''' Whoops, I should really read the Talk page before posting; the previous post by TMC already has all of this. Thanks for the link on I/O mapping; it's informative!<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, using FB_RTERROR... I can get Free Basic to compile and install. Now I'm on to compiling GAME. One problem that keeps coming up is an error similar to the following:<br />
<br />
/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/lib/gcc/arm-unknown-linux-gnueabi/4.7.3/../../../../arm-unknown-linux-gnueabi/bin/ld: warning: libpulse-simple.so.0, needed by /home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf/libSDL.so, not found (try using -rpath or -rpath-link)<br />
<br />
This is annoying, because I have libpulse-simple.so.0, and I have "-L/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf" in my C/CXXFLAGS. I tried adding this to SConscript:<br />
<br />
LDFLAGS = '-rpath-link="/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf"'<br />
<br />
..but it doesn't seem to help. Right now, I am simply adding every library manually to, e.g., SDL's list:<br />
<br />
'sdl':<br />
{'shared_modules': 'music_sdl.bas sdl_lumprwops.bas',<br />
'libraries': 'SDL SDL_mixer asound pulse-simple pulse directfb fusion direct caca ts mikmod vorbisfile vorbis FLAC'}<br />
<br />
Any ideas on getting this working would be very helpful.<br />
<br />
'''Edit:''' But don't think too hard; I'm probably just going to chroot my way around this problem regardless<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Did you add "-L/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf" to C/CXXFLAGS, or to CXXLINKFLAGS? You should add it to the later (in SConscript), that's what's actually used. And that really ought to be changed to LDFLAGS instead. (SConscript's philosophy is to ignore the environment that scons is invoked in in order to not depend on it, so generally our build script ignores env variables too.)<br />
<br />
Other than that, I actually don't really know much about all of that, and just fiddle with things until they work.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've tried each of those, and it doesn't help (thanks though). I'm in the middle of preparing a chroot for this (it's unrelated; I'm getting hard-includes to "/lib" and "/usr" as top-levels, so I figure a chroot is the only way to go). I'll post back with more results later.<br />
<br />
<br />
==First Successful Build with RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Don't get too excited; graphics don't work. But here's how I got it to compile.<br />
<br />
First, make a directory like so:<br />
mkdir ~/rpi_sync<br />
export RPI_HOME=/home/YOU/rpi_sync<br />
<br />
We'll do everything in this directory, so it should be relatively sane. <br />
<br />
Now, connect to your Pi and yank some stuff. The IP address will change, of course.<br />
cd $RPI_HOME<br />
rsync -rl pi@192.168.1.5:/usr .<br />
rsync -rl pi@192.168.1.5:/lib .<br />
<br />
So now "lib" and "usr" are copies of the Pi's. We need two more directories; one for our source and one for installed cross-compiling tools (fbc and gcc).<br />
cd $RPI_HOME<br />
mkdir source<br />
mkdir opt<br />
<br />
Let's make our compiler. We are using crosstool-ng, since it's the most common option. Follow this page:<br />
http://www.kitware.com/blog/home/post/426<br />
...except for the "prefix directory" choose:<br />
${RPI_HOME}/opt/${CT_TARGET}.<br />
<br />
In order to make the setting of environment variables slightly less painful, I've made a simple "wish-list", called "set_vars.source". You should copy/save this file to your $RPI_HOME directory:<br />
http://pastie.org/8076443<br />
<br />
From now on, when you open a terminal for compiling, do the following:<br />
cd $RPI_HOME<br />
source set_vars.source<br />
<br />
Now we need FreeBasic. Get the source:<br />
cd $RPI_HOME/source<br />
git clone git://git.code.sf.net/u/teeemcee/fbc freebasic<br />
<br />
You'll need to mangle things a bit. Open "freebasic/src/rtlib/linux/sys_portio.c", and change it to look like this:<br />
/* ports I/O functions */<br />
#include "../fb.h"<br />
#include "../unix/fb_private_console.h"<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
int fb_hOut( unsigned short port, unsigned char value ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
Now, create a file called "config.mk" in the "freebasic" directory and give it the following contents:<br />
ENABLE_PREFIX=1<br />
prefix=${RPI_HOME}/opt/fbc-0.90-android<br />
V=1<br />
<br />
Note: I know "android" is wrong here, but it's just a visual error, and I don't want to go scouring my ENV.<br />
<br />
Now, compile and install Freebasic as explained in the main page:<br />
make compiler<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
make install-compiler install-rtlib install-includes<br />
<br />
We're ready to compile the OHR's source. Grab it:<br />
cd $RPI_HOME/source<br />
svn co svn://gilgamesh.hamsterrepublic.com/ohrrpgce<br />
<br />
We need to change a few things. Open "ohrrpgce/wip/SConscript" and replace its text with the following:<br />
http://pastie.org/8076462<br />
<br />
Note: These changes are not the most elegant, and I would not recommend merging them. Essentially, they are all of the "arm+linux instead of arm+android" variety.<br />
<br />
There's one more thing you need to do, and it's ugly:<br />
sudo mkdir /lib/arm-linux-gnueabihf<br />
sudo ln -s ${RPI_HOME}/lib/arm-linux-gnueabihf/ld-linux-armhf.so.3 /lib/arm-linux-gnueabihf<br />
sudo ln -s ${RPI_HOME}/lib/arm-linux-gnueabihf/libc.so.6 /lib/arm-linux-gnueabihf<br />
sudo ln -s ${RPI_HOME}/lib/arm-linux-gnueabihf/libpthread.so.0 /lib/arm-linux-gnueabihf<br />
sudo mkdir /usr/lib/arm-linux-gnueabihf<br />
sudo ln -s ${RPI_HOME}/usr/lib/arm-linux-gnueabihf/libc_nonshared.a /usr/lib/arm-linux-gnueabihf<br />
sudo ln -s ${RPI_HOME}/usr/lib/arm-linux-gnueabihf/libpthread_nonshared.a /usr/lib/arm-linux-gnueabihf<br />
<br />
This probably won't mess up your system, since your compiler will never look in "arm-linux-gnueabihf" for libraries. Anyway, I don't know what's considered the "right" way to handle this.<br />
<br />
Now, build it:<br />
cd $RPI_HOME/source/ohrrpgce/wip<br />
scons fbc=$FBC rpi=1 debug=1 game<br />
<br />
Et voila!<br />
<br />
Of course it doesn't actually work; here's an strace:<br />
http://pastebin.com/bp8nND16<br />
<br />
...and an latrace:<br />
http://pastebin.com/N1GjRt0E<br />
<br />
More debugging to follow.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=29017
Talk:Compiling for Android
2013-06-24T21:51:56Z
<p>Sorlok reaves: /* Success, of a kind. */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That definitely should be possible! However I see lots of little changes are needed. Have you got a Raspberry Pi? I'd love to see it happen.<br />
<br />
AFAIK android uses identical ABIs to other ELF unix OSes on the same CPU architectures (though I get the hint that there may be slight differences in the linker due to some platforms not following the standards to the letter).<br />
<br />
My changes to both FB and the OHR conflate Android and ARM in several places. You would want to compile both for a GNU/Linux target instead of Android (if you run a normal GNU/Linux distrib with ncurses and X11 on the RPi). SConscript needs several changes in addition to the ones I just made. <s>If SDL on the RPi uses the frame buffer, then you may not want to link with the X11 libs</s> (actually libfb still links to X11 by default, unless you add -DDISABLE_X11 to config.mk). And it'll need to know where to find the FB libs (it looks like the Raspberry Pi toolchain uses a platform triple of arm-bcm2708hardfp-linux-gnueabi, so you'll probably edit SConscript where I hardcoded arm-linux-androideabi). Looking through my changes to FB, it looks like the only thing which needs changing for GNU/Linux on ARM is to edit rtlib/static/fbrt0.c to use __attribute__((constructor)). fbc should actually be fine for cross compiling to any 32 bit target as long as it's invoked with "-arch arm" (well not quite true: it assumes that it knows how all structures are laid out in memory (that they are the same as laid out by GCC on x86... I'm surprised this hasn't blown up on ARM!)<br />
<br />
Look at compile-using-toolchain.sh for how to invoke scons (you'd build Game/Custom directly using scons). You'd probably also need to add the directory containing a build of libSDL.so (and libSDL_mixer.so (or use music=silence)) cross-compiled to the Raspberry Pi to the CXXLINKFLAGS variable in SConscript (we ought to set the standard LDFLAGS variable instead).<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've got one coming in the mail ---and I noticed you separated the Android/ARM targets in a recent commit, thanks! I'll give it a whirl once my device arrives.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool. I might get around to some other clean up before then. Those SConscript changes were just one thing needed.<br />
<br />
<br />
==Hacking my way through the RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): So I've started working on cross-compiling for Raspberry PI. I'm stuck on the "make rtlib" step of FreeBasic (I'll work through it more later, but I figured I've reached a stopping point for tonight so might as well document it.)<br />
<br />
First, I installed crosstools-ng, which is by far the most common suggestion for setting up a toolchain. I followed this post for the most part:<br />
http://www.kitware.com/blog/home/post/426<br />
<br />
Then, I ssh-ed into the RPi and installed libSDL1.2-dev, and all related libraries. Then I did this:<br />
cd ~/opt/rpi_sync<br />
rsync -rl pi@192.168.1.110:/usr .<br />
<br />
Following that, I checked out the FreeBasic repo listed above and used this config.mk file:<br />
ENABLE_PREFIX=1<br />
prefix=/home/sethhetu/opt/fbc-0.90-rpi<br />
PATH:=/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/bin:$(PATH)<br />
CFLAGS := -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf<br />
TARGET_PREFIX=arm-unknown-linux-gnueabi-<br />
V=1<br />
<br />
Note that CFLAGS; I was guessing that this would allow me to use the same headers on both the RPi and my laptop. This is just a guess!<br />
<br />
At that point, I ran:<br />
make compiler<br />
<br />
It ran to completion, then:<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
<br />
It fails with:<br />
arm-unknown-linux-gnueabi-gcc -Wall -Werror-implicit-function-declaration -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf -c src/rtlib/linux/sys_portio.c -o src/rtlib/arm-unknown-linux-gnueabi-obj/sys_portio.o<br />
src/rtlib/linux/sys_portio.c: In function 'fb_hIn':<br />
src/rtlib/linux/sys_portio.c:11:2: error: impossible constraint in 'asm'<br />
<br />
The line in question:<br />
__asm__ volatile ("inb %1, %0" : "=a" (value) : "d" (port));<br />
<br />
Here's where I'm stumped. If it was a header-file error, I wouldn't be surprised. But it's a c file, and involves the correct toolchain item (arm-unknown-linux-gnueabi-gcc). And the same FreeBasic repo compiled fine for Android with a similar config.mk (but no CFLAGS in this case).<br />
<br />
Anyway, I'll debug more this weekend. Any suggestions would be appreciated.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool.<br />
<br />
I hadn't noticed that there was asm in use there. To do things properly, we'll want to move that stuff into an x86-specific file, and also provide an ARM implementation of port IO. I assume that anyone using a RPi for hardware hacking and using FB will want ARM implementations of that. I don't know anything about that stuff. But it apears that ARM doesn't have separate IO instructions. Someone said:<br />
The ARM architecture doesn't specify a separate I/O space -- everything is memory-mapped, so:<br />
1) There are no dedicated (fast-path) I/O instructions -- load and store is all you get.<br />
For example see [http://elinux.org/RPi_Low-level_peripherals#C_2 here] for an example of accessing the RPi GPIO pins using memory mapping. Well, I'll ask on the FB forums, there are people there who care about that.<br />
<br />
Anyway, just comment out that assembly. If you look at rtlib/android/sys_portio.c you'll see the functions throw errors.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just adding this brief tidbit; the Android-compiled version has:<br />
<br />
arm-linux-androideabi-gcc -DENABLE_MT -Wall -Werror-implicit-function-declaration -DDISABLE_FFI -DDISABLE_X11 -DDISABLE_NCURSES -Wfatal-errors -O2 -c src/rtlib/android/sys_portio.c -o src/rtlib/arm-linux-androideabi-objmt/sys_portio.o<br />
<br />
...which contains:<br />
<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
...etc. I'm sure you (TMC) know about this; I'm just adding it for clarity's sake to anyone else reading along.<br />
<br />
No time to work on this today, but I'll probably use FB_RTERROR_ILLEGALFUNCTIONCALL in the linux/sys_portio.c when I get a chance.<br />
<br />
'''Edit:''' Whoops, I should really read the Talk page before posting; the previous post by TMC already has all of this. Thanks for the link on I/O mapping; it's informative!<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, using FB_RTERROR... I can get Free Basic to compile and install. Now I'm on to compiling GAME. One problem that keeps coming up is an error similar to the following:<br />
<br />
/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/lib/gcc/arm-unknown-linux-gnueabi/4.7.3/../../../../arm-unknown-linux-gnueabi/bin/ld: warning: libpulse-simple.so.0, needed by /home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf/libSDL.so, not found (try using -rpath or -rpath-link)<br />
<br />
This is annoying, because I have libpulse-simple.so.0, and I have "-L/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf" in my C/CXXFLAGS. I tried adding this to SConscript:<br />
<br />
LDFLAGS = '-rpath-link="/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf"'<br />
<br />
..but it doesn't seem to help. Right now, I am simply adding every library manually to, e.g., SDL's list:<br />
<br />
'sdl':<br />
{'shared_modules': 'music_sdl.bas sdl_lumprwops.bas',<br />
'libraries': 'SDL SDL_mixer asound pulse-simple pulse directfb fusion direct caca ts mikmod vorbisfile vorbis FLAC'}<br />
<br />
Any ideas on getting this working would be very helpful.<br />
<br />
'''Edit:''' But don't think too hard; I'm probably just going to chroot my way around this problem regardless<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Did you add "-L/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf" to C/CXXFLAGS, or to CXXLINKFLAGS? You should add it to the later (in SConscript), that's what's actually used. And that really ought to be changed to LDFLAGS instead. (SConscript's philosophy is to ignore the environment that scons is invoked in in order to not depend on it, so generally our build script ignores env variables too.)<br />
<br />
Other than that, I actually don't really know much about all of that, and just fiddle with things until they work.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've tried each of those, and it doesn't help (thanks though). I'm in the middle of preparing a chroot for this (it's unrelated; I'm getting hard-includes to "/lib" and "/usr" as top-levels, so I figure a chroot is the only way to go). I'll post back with more results later.<br />
<br />
<br />
==First Successful Build with RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Don't get too excited; graphics don't work. But here's how I got it to compile.<br />
<br />
First, make a directory like so:<br />
mkdir ~/rpi_sync<br />
export RPI_HOME=/home/YOU/rpi_sync<br />
<br />
We'll do everything in this directory, so it should be relatively sane. <br />
<br />
Now, connect to your Pi and yank some stuff. The IP address will change, of course.<br />
cd $RPI_HOME<br />
rsync -rl pi@192.168.1.5:/usr .<br />
rsync -rl pi@192.168.1.5:/lib .<br />
<br />
So now "lib" and "usr" are copies of the Pi's. We need two more directories; one for our source and one for installed cross-compiling tools (fbc and gcc).<br />
cd $RPI_HOME<br />
mkdir source<br />
mkdir opt<br />
<br />
Let's make our compiler. We are using crosstool-ng, since it's the most common option. Follow this page:<br />
http://www.kitware.com/blog/home/post/426<br />
...except for the "prefix directory" choose:<br />
${RPI_HOME}/opt/${CT_TARGET}.<br />
<br />
In order to make the setting of environment variables slightly less painful, I've made a simple "wish-list", called "set_vars.source". You should copy/save this file to your $RPI_HOME directory:<br />
http://pastie.org/8076443<br />
<br />
From now on, when you open a terminal for compiling, do the following:<br />
cd $RPI_HOME<br />
source set_vars.source<br />
<br />
Now we need FreeBasic. Get the source:<br />
cd $RPI_HOME/source<br />
git clone git://git.code.sf.net/u/teeemcee/fbc freebasic<br />
<br />
You'll need to mangle things a bit. Open "freebasic/src/rtlib/linux/sys_portio.c", and change it to look like this:<br />
/* ports I/O functions */<br />
#include "../fb.h"<br />
#include "../unix/fb_private_console.h"<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
int fb_hOut( unsigned short port, unsigned char value ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
Now, create a file called "config.mk" in the "freebasic" directory and give it the following contents:<br />
ENABLE_PREFIX=1<br />
prefix=${RPI_HOME}/opt/fbc-0.90-android<br />
V=1<br />
<br />
Note: I know "android" is wrong here, but it's just a visual error, and I don't want to go scouring my ENV.<br />
<br />
Now, compile and install Freebasic as explained in the main page:<br />
make compiler<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
make install-compiler install-rtlib install-includes<br />
<br />
We're ready to compile the OHR's source. Grab it:<br />
cd $RPI_HOME/source<br />
svn co svn://gilgamesh.hamsterrepublic.com/ohrrpgce<br />
<br />
We need to change a few things. Open "ohrrpgce/wip/SConscript" and replace its text with the following:<br />
http://pastie.org/8076462<br />
<br />
Note: These changes are not the most elegant, and I would not recommend merging them. Essentially, they are all of the "arm+linux instead of arm+android" variety.<br />
<br />
Now, build it:<br />
cd $RPI_HOME/source/ohrrpgce/wip<br />
scons fbc=$FBC rpi=1 debug=1 game<br />
<br />
Et voila!<br />
<br />
Of course it doesn't actually work; here's an strace:<br />
http://pastebin.com/bp8nND16<br />
<br />
...and an latrace:<br />
http://pastebin.com/N1GjRt0E<br />
<br />
More debugging to follow.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=29016
Talk:Compiling for Android
2013-06-24T13:33:03Z
<p>Sorlok reaves: /* no dice (yet) */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That definitely should be possible! However I see lots of little changes are needed. Have you got a Raspberry Pi? I'd love to see it happen.<br />
<br />
AFAIK android uses identical ABIs to other ELF unix OSes on the same CPU architectures (though I get the hint that there may be slight differences in the linker due to some platforms not following the standards to the letter).<br />
<br />
My changes to both FB and the OHR conflate Android and ARM in several places. You would want to compile both for a GNU/Linux target instead of Android (if you run a normal GNU/Linux distrib with ncurses and X11 on the RPi). SConscript needs several changes in addition to the ones I just made. <s>If SDL on the RPi uses the frame buffer, then you may not want to link with the X11 libs</s> (actually libfb still links to X11 by default, unless you add -DDISABLE_X11 to config.mk). And it'll need to know where to find the FB libs (it looks like the Raspberry Pi toolchain uses a platform triple of arm-bcm2708hardfp-linux-gnueabi, so you'll probably edit SConscript where I hardcoded arm-linux-androideabi). Looking through my changes to FB, it looks like the only thing which needs changing for GNU/Linux on ARM is to edit rtlib/static/fbrt0.c to use __attribute__((constructor)). fbc should actually be fine for cross compiling to any 32 bit target as long as it's invoked with "-arch arm" (well not quite true: it assumes that it knows how all structures are laid out in memory (that they are the same as laid out by GCC on x86... I'm surprised this hasn't blown up on ARM!)<br />
<br />
Look at compile-using-toolchain.sh for how to invoke scons (you'd build Game/Custom directly using scons). You'd probably also need to add the directory containing a build of libSDL.so (and libSDL_mixer.so (or use music=silence)) cross-compiled to the Raspberry Pi to the CXXLINKFLAGS variable in SConscript (we ought to set the standard LDFLAGS variable instead).<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've got one coming in the mail ---and I noticed you separated the Android/ARM targets in a recent commit, thanks! I'll give it a whirl once my device arrives.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool. I might get around to some other clean up before then. Those SConscript changes were just one thing needed.<br />
<br />
<br />
==Hacking my way through the RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): So I've started working on cross-compiling for Raspberry PI. I'm stuck on the "make rtlib" step of FreeBasic (I'll work through it more later, but I figured I've reached a stopping point for tonight so might as well document it.)<br />
<br />
First, I installed crosstools-ng, which is by far the most common suggestion for setting up a toolchain. I followed this post for the most part:<br />
http://www.kitware.com/blog/home/post/426<br />
<br />
Then, I ssh-ed into the RPi and installed libSDL1.2-dev, and all related libraries. Then I did this:<br />
cd ~/opt/rpi_sync<br />
rsync -rl pi@192.168.1.110:/usr .<br />
<br />
Following that, I checked out the FreeBasic repo listed above and used this config.mk file:<br />
ENABLE_PREFIX=1<br />
prefix=/home/sethhetu/opt/fbc-0.90-rpi<br />
PATH:=/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/bin:$(PATH)<br />
CFLAGS := -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf<br />
TARGET_PREFIX=arm-unknown-linux-gnueabi-<br />
V=1<br />
<br />
Note that CFLAGS; I was guessing that this would allow me to use the same headers on both the RPi and my laptop. This is just a guess!<br />
<br />
At that point, I ran:<br />
make compiler<br />
<br />
It ran to completion, then:<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
<br />
It fails with:<br />
arm-unknown-linux-gnueabi-gcc -Wall -Werror-implicit-function-declaration -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf -c src/rtlib/linux/sys_portio.c -o src/rtlib/arm-unknown-linux-gnueabi-obj/sys_portio.o<br />
src/rtlib/linux/sys_portio.c: In function 'fb_hIn':<br />
src/rtlib/linux/sys_portio.c:11:2: error: impossible constraint in 'asm'<br />
<br />
The line in question:<br />
__asm__ volatile ("inb %1, %0" : "=a" (value) : "d" (port));<br />
<br />
Here's where I'm stumped. If it was a header-file error, I wouldn't be surprised. But it's a c file, and involves the correct toolchain item (arm-unknown-linux-gnueabi-gcc). And the same FreeBasic repo compiled fine for Android with a similar config.mk (but no CFLAGS in this case).<br />
<br />
Anyway, I'll debug more this weekend. Any suggestions would be appreciated.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool.<br />
<br />
I hadn't noticed that there was asm in use there. To do things properly, we'll want to move that stuff into an x86-specific file, and also provide an ARM implementation of port IO. I assume that anyone using a RPi for hardware hacking and using FB will want ARM implementations of that. I don't know anything about that stuff. But it apears that ARM doesn't have separate IO instructions. Someone said:<br />
The ARM architecture doesn't specify a separate I/O space -- everything is memory-mapped, so:<br />
1) There are no dedicated (fast-path) I/O instructions -- load and store is all you get.<br />
For example see [http://elinux.org/RPi_Low-level_peripherals#C_2 here] for an example of accessing the RPi GPIO pins using memory mapping. Well, I'll ask on the FB forums, there are people there who care about that.<br />
<br />
Anyway, just comment out that assembly. If you look at rtlib/android/sys_portio.c you'll see the functions throw errors.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just adding this brief tidbit; the Android-compiled version has:<br />
<br />
arm-linux-androideabi-gcc -DENABLE_MT -Wall -Werror-implicit-function-declaration -DDISABLE_FFI -DDISABLE_X11 -DDISABLE_NCURSES -Wfatal-errors -O2 -c src/rtlib/android/sys_portio.c -o src/rtlib/arm-linux-androideabi-objmt/sys_portio.o<br />
<br />
...which contains:<br />
<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
...etc. I'm sure you (TMC) know about this; I'm just adding it for clarity's sake to anyone else reading along.<br />
<br />
No time to work on this today, but I'll probably use FB_RTERROR_ILLEGALFUNCTIONCALL in the linux/sys_portio.c when I get a chance.<br />
<br />
'''Edit:''' Whoops, I should really read the Talk page before posting; the previous post by TMC already has all of this. Thanks for the link on I/O mapping; it's informative!<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, using FB_RTERROR... I can get Free Basic to compile and install. Now I'm on to compiling GAME. One problem that keeps coming up is an error similar to the following:<br />
<br />
/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/lib/gcc/arm-unknown-linux-gnueabi/4.7.3/../../../../arm-unknown-linux-gnueabi/bin/ld: warning: libpulse-simple.so.0, needed by /home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf/libSDL.so, not found (try using -rpath or -rpath-link)<br />
<br />
This is annoying, because I have libpulse-simple.so.0, and I have "-L/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf" in my C/CXXFLAGS. I tried adding this to SConscript:<br />
<br />
LDFLAGS = '-rpath-link="/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf"'<br />
<br />
..but it doesn't seem to help. Right now, I am simply adding every library manually to, e.g., SDL's list:<br />
<br />
'sdl':<br />
{'shared_modules': 'music_sdl.bas sdl_lumprwops.bas',<br />
'libraries': 'SDL SDL_mixer asound pulse-simple pulse directfb fusion direct caca ts mikmod vorbisfile vorbis FLAC'}<br />
<br />
Any ideas on getting this working would be very helpful.<br />
<br />
'''Edit:''' But don't think too hard; I'm probably just going to chroot my way around this problem regardless<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Did you add "-L/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf" to C/CXXFLAGS, or to CXXLINKFLAGS? You should add it to the later (in SConscript), that's what's actually used. And that really ought to be changed to LDFLAGS instead. (SConscript's philosophy is to ignore the environment that scons is invoked in in order to not depend on it, so generally our build script ignores env variables too.)<br />
<br />
Other than that, I actually don't really know much about all of that, and just fiddle with things until they work.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've tried each of those, and it doesn't help (thanks though). I'm in the middle of preparing a chroot for this (it's unrelated; I'm getting hard-includes to "/lib" and "/usr" as top-levels, so I figure a chroot is the only way to go). I'll post back with more results later.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=29014
Talk:Compiling for Android
2013-06-23T19:00:47Z
<p>Sorlok reaves: /* idle thought */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That definitely should be possible! However I see lots of little changes are needed. Have you got a Raspberry Pi? I'd love to see it happen.<br />
<br />
AFAIK android uses identical ABIs to other ELF unix OSes on the same CPU architectures (though I get the hint that there may be slight differences in the linker due to some platforms not following the standards to the letter).<br />
<br />
My changes to both FB and the OHR conflate Android and ARM in several places. You would want to compile both for a GNU/Linux target instead of Android (if you run a normal GNU/Linux distrib with ncurses and X11 on the RPi). SConscript needs several changes in addition to the ones I just made. <s>If SDL on the RPi uses the frame buffer, then you may not want to link with the X11 libs</s> (actually libfb still links to X11 by default, unless you add -DDISABLE_X11 to config.mk). And it'll need to know where to find the FB libs (it looks like the Raspberry Pi toolchain uses a platform triple of arm-bcm2708hardfp-linux-gnueabi, so you'll probably edit SConscript where I hardcoded arm-linux-androideabi). Looking through my changes to FB, it looks like the only thing which needs changing for GNU/Linux on ARM is to edit rtlib/static/fbrt0.c to use __attribute__((constructor)). fbc should actually be fine for cross compiling to any 32 bit target as long as it's invoked with "-arch arm" (well not quite true: it assumes that it knows how all structures are laid out in memory (that they are the same as laid out by GCC on x86... I'm surprised this hasn't blown up on ARM!)<br />
<br />
Look at compile-using-toolchain.sh for how to invoke scons (you'd build Game/Custom directly using scons). You'd probably also need to add the directory containing a build of libSDL.so (and libSDL_mixer.so (or use music=silence)) cross-compiled to the Raspberry Pi to the CXXLINKFLAGS variable in SConscript (we ought to set the standard LDFLAGS variable instead).<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've got one coming in the mail ---and I noticed you separated the Android/ARM targets in a recent commit, thanks! I'll give it a whirl once my device arrives.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool. I might get around to some other clean up before then. Those SConscript changes were just one thing needed.<br />
<br />
<br />
==Hacking my way through the RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): So I've started working on cross-compiling for Raspberry PI. I'm stuck on the "make rtlib" step of FreeBasic (I'll work through it more later, but I figured I've reached a stopping point for tonight so might as well document it.)<br />
<br />
First, I installed crosstools-ng, which is by far the most common suggestion for setting up a toolchain. I followed this post for the most part:<br />
http://www.kitware.com/blog/home/post/426<br />
<br />
Then, I ssh-ed into the RPi and installed libSDL1.2-dev, and all related libraries. Then I did this:<br />
cd ~/opt/rpi_sync<br />
rsync -rl pi@192.168.1.110:/usr .<br />
<br />
Following that, I checked out the FreeBasic repo listed above and used this config.mk file:<br />
ENABLE_PREFIX=1<br />
prefix=/home/sethhetu/opt/fbc-0.90-rpi<br />
PATH:=/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/bin:$(PATH)<br />
CFLAGS := -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf<br />
TARGET_PREFIX=arm-unknown-linux-gnueabi-<br />
V=1<br />
<br />
Note that CFLAGS; I was guessing that this would allow me to use the same headers on both the RPi and my laptop. This is just a guess!<br />
<br />
At that point, I ran:<br />
make compiler<br />
<br />
It ran to completion, then:<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
<br />
It fails with:<br />
arm-unknown-linux-gnueabi-gcc -Wall -Werror-implicit-function-declaration -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf -c src/rtlib/linux/sys_portio.c -o src/rtlib/arm-unknown-linux-gnueabi-obj/sys_portio.o<br />
src/rtlib/linux/sys_portio.c: In function 'fb_hIn':<br />
src/rtlib/linux/sys_portio.c:11:2: error: impossible constraint in 'asm'<br />
<br />
The line in question:<br />
__asm__ volatile ("inb %1, %0" : "=a" (value) : "d" (port));<br />
<br />
Here's where I'm stumped. If it was a header-file error, I wouldn't be surprised. But it's a c file, and involves the correct toolchain item (arm-unknown-linux-gnueabi-gcc). And the same FreeBasic repo compiled fine for Android with a similar config.mk (but no CFLAGS in this case).<br />
<br />
Anyway, I'll debug more this weekend. Any suggestions would be appreciated.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool.<br />
<br />
I hadn't noticed that there was asm in use there. To do things properly, we'll want to move that stuff into an x86-specific file, and also provide an ARM implementation of port IO. I assume that anyone using a RPi for hardware hacking and using FB will want ARM implementations of that. I don't know anything about that stuff. But it apears that ARM doesn't have separate IO instructions. Someone said:<br />
The ARM architecture doesn't specify a separate I/O space -- everything is memory-mapped, so:<br />
1) There are no dedicated (fast-path) I/O instructions -- load and store is all you get.<br />
For example see [http://elinux.org/RPi_Low-level_peripherals#C_2 here] for an example of accessing the RPi GPIO pins using memory mapping. Well, I'll ask on the FB forums, there are people there who care about that.<br />
<br />
Anyway, just comment out that assembly. If you look at rtlib/android/sys_portio.c you'll see the functions throw errors.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just adding this brief tidbit; the Android-compiled version has:<br />
<br />
arm-linux-androideabi-gcc -DENABLE_MT -Wall -Werror-implicit-function-declaration -DDISABLE_FFI -DDISABLE_X11 -DDISABLE_NCURSES -Wfatal-errors -O2 -c src/rtlib/android/sys_portio.c -o src/rtlib/arm-linux-androideabi-objmt/sys_portio.o<br />
<br />
...which contains:<br />
<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
...etc. I'm sure you (TMC) know about this; I'm just adding it for clarity's sake to anyone else reading along.<br />
<br />
No time to work on this today, but I'll probably use FB_RTERROR_ILLEGALFUNCTIONCALL in the linux/sys_portio.c when I get a chance.<br />
<br />
'''Edit:''' Whoops, I should really read the Talk page before posting; the previous post by TMC already has all of this. Thanks for the link on I/O mapping; it's informative!<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, using FB_RTERROR... I can get Free Basic to compile and install. Now I'm on to compiling GAME. One problem that keeps coming up is an error similar to the following:<br />
<br />
/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/lib/gcc/arm-unknown-linux-gnueabi/4.7.3/../../../../arm-unknown-linux-gnueabi/bin/ld: warning: libpulse-simple.so.0, needed by /home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf/libSDL.so, not found (try using -rpath or -rpath-link)<br />
<br />
This is annoying, because I have libpulse-simple.so.0, and I have "-L/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf" in my C/CXXFLAGS. I tried adding this to SConscript:<br />
<br />
LDFLAGS = '-rpath-link="/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf"'<br />
<br />
..but it doesn't seem to help. Right now, I am simply adding every library manually to, e.g., SDL's list:<br />
<br />
'sdl':<br />
{'shared_modules': 'music_sdl.bas sdl_lumprwops.bas',<br />
'libraries': 'SDL SDL_mixer asound pulse-simple pulse directfb fusion direct caca ts mikmod vorbisfile vorbis FLAC'}<br />
<br />
Any ideas on getting this working would be very helpful.<br />
<br />
'''Edit:''' But don't think too hard; I'm probably just going to chroot my way around this problem regardless</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=29013
Talk:Compiling for Android
2013-06-23T18:17:55Z
<p>Sorlok reaves: /* Automatic linking of library sub-dependencies? *?</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That definitely should be possible! However I see lots of little changes are needed. Have you got a Raspberry Pi? I'd love to see it happen.<br />
<br />
AFAIK android uses identical ABIs to other ELF unix OSes on the same CPU architectures (though I get the hint that there may be slight differences in the linker due to some platforms not following the standards to the letter).<br />
<br />
My changes to both FB and the OHR conflate Android and ARM in several places. You would want to compile both for a GNU/Linux target instead of Android (if you run a normal GNU/Linux distrib with ncurses and X11 on the RPi). SConscript needs several changes in addition to the ones I just made. <s>If SDL on the RPi uses the frame buffer, then you may not want to link with the X11 libs</s> (actually libfb still links to X11 by default, unless you add -DDISABLE_X11 to config.mk). And it'll need to know where to find the FB libs (it looks like the Raspberry Pi toolchain uses a platform triple of arm-bcm2708hardfp-linux-gnueabi, so you'll probably edit SConscript where I hardcoded arm-linux-androideabi). Looking through my changes to FB, it looks like the only thing which needs changing for GNU/Linux on ARM is to edit rtlib/static/fbrt0.c to use __attribute__((constructor)). fbc should actually be fine for cross compiling to any 32 bit target as long as it's invoked with "-arch arm" (well not quite true: it assumes that it knows how all structures are laid out in memory (that they are the same as laid out by GCC on x86... I'm surprised this hasn't blown up on ARM!)<br />
<br />
Look at compile-using-toolchain.sh for how to invoke scons (you'd build Game/Custom directly using scons). You'd probably also need to add the directory containing a build of libSDL.so (and libSDL_mixer.so (or use music=silence)) cross-compiled to the Raspberry Pi to the CXXLINKFLAGS variable in SConscript (we ought to set the standard LDFLAGS variable instead).<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've got one coming in the mail ---and I noticed you separated the Android/ARM targets in a recent commit, thanks! I'll give it a whirl once my device arrives.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool. I might get around to some other clean up before then. Those SConscript changes were just one thing needed.<br />
<br />
<br />
==Hacking my way through the RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): So I've started working on cross-compiling for Raspberry PI. I'm stuck on the "make rtlib" step of FreeBasic (I'll work through it more later, but I figured I've reached a stopping point for tonight so might as well document it.)<br />
<br />
First, I installed crosstools-ng, which is by far the most common suggestion for setting up a toolchain. I followed this post for the most part:<br />
http://www.kitware.com/blog/home/post/426<br />
<br />
Then, I ssh-ed into the RPi and installed libSDL1.2-dev, and all related libraries. Then I did this:<br />
cd ~/opt/rpi_sync<br />
rsync -rl pi@192.168.1.110:/usr .<br />
<br />
Following that, I checked out the FreeBasic repo listed above and used this config.mk file:<br />
ENABLE_PREFIX=1<br />
prefix=/home/sethhetu/opt/fbc-0.90-rpi<br />
PATH:=/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/bin:$(PATH)<br />
CFLAGS := -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf<br />
TARGET_PREFIX=arm-unknown-linux-gnueabi-<br />
V=1<br />
<br />
Note that CFLAGS; I was guessing that this would allow me to use the same headers on both the RPi and my laptop. This is just a guess!<br />
<br />
At that point, I ran:<br />
make compiler<br />
<br />
It ran to completion, then:<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
<br />
It fails with:<br />
arm-unknown-linux-gnueabi-gcc -Wall -Werror-implicit-function-declaration -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf -c src/rtlib/linux/sys_portio.c -o src/rtlib/arm-unknown-linux-gnueabi-obj/sys_portio.o<br />
src/rtlib/linux/sys_portio.c: In function 'fb_hIn':<br />
src/rtlib/linux/sys_portio.c:11:2: error: impossible constraint in 'asm'<br />
<br />
The line in question:<br />
__asm__ volatile ("inb %1, %0" : "=a" (value) : "d" (port));<br />
<br />
Here's where I'm stumped. If it was a header-file error, I wouldn't be surprised. But it's a c file, and involves the correct toolchain item (arm-unknown-linux-gnueabi-gcc). And the same FreeBasic repo compiled fine for Android with a similar config.mk (but no CFLAGS in this case).<br />
<br />
Anyway, I'll debug more this weekend. Any suggestions would be appreciated.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool.<br />
<br />
I hadn't noticed that there was asm in use there. To do things properly, we'll want to move that stuff into an x86-specific file, and also provide an ARM implementation of port IO. I assume that anyone using a RPi for hardware hacking and using FB will want ARM implementations of that. I don't know anything about that stuff. But it apears that ARM doesn't have separate IO instructions. Someone said:<br />
The ARM architecture doesn't specify a separate I/O space -- everything is memory-mapped, so:<br />
1) There are no dedicated (fast-path) I/O instructions -- load and store is all you get.<br />
For example see [http://elinux.org/RPi_Low-level_peripherals#C_2 here] for an example of accessing the RPi GPIO pins using memory mapping. Well, I'll ask on the FB forums, there are people there who care about that.<br />
<br />
Anyway, just comment out that assembly. If you look at rtlib/android/sys_portio.c you'll see the functions throw errors.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just adding this brief tidbit; the Android-compiled version has:<br />
<br />
arm-linux-androideabi-gcc -DENABLE_MT -Wall -Werror-implicit-function-declaration -DDISABLE_FFI -DDISABLE_X11 -DDISABLE_NCURSES -Wfatal-errors -O2 -c src/rtlib/android/sys_portio.c -o src/rtlib/arm-linux-androideabi-objmt/sys_portio.o<br />
<br />
...which contains:<br />
<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
...etc. I'm sure you (TMC) know about this; I'm just adding it for clarity's sake to anyone else reading along.<br />
<br />
No time to work on this today, but I'll probably use FB_RTERROR_ILLEGALFUNCTIONCALL in the linux/sys_portio.c when I get a chance.<br />
<br />
'''Edit:''' Whoops, I should really read the Talk page before posting; the previous post by TMC already has all of this. Thanks for the link on I/O mapping; it's informative!<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, using FB_RTERROR... I can get Free Basic to compile and install. Now I'm on to compiling GAME. One problem that keeps coming up is an error similar to the following:<br />
<br />
/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/lib/gcc/arm-unknown-linux-gnueabi/4.7.3/../../../../arm-unknown-linux-gnueabi/bin/ld: warning: libpulse-simple.so.0, needed by /home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf/libSDL.so, not found (try using -rpath or -rpath-link)<br />
<br />
This is annoying, because I have libpulse-simple.so.0, and I have "-L/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf" in my C/CXXFLAGS. I tried adding this to SConscript:<br />
<br />
LDFLAGS = '-rpath-link="/home/sethhetu/opt/rpi_sync/usr/lib/arm-linux-gnueabihf"'<br />
<br />
..but it doesn't seem to help. Right now, I am simply adding every library manually to, e.g., SDL's list:<br />
<br />
'sdl':<br />
{'shared_modules': 'music_sdl.bas sdl_lumprwops.bas',<br />
'libraries': 'SDL SDL_mixer asound pulse-simple pulse directfb fusion direct caca ts mikmod vorbisfile vorbis FLAC'}<br />
<br />
Any ideas on getting this working would be very helpful.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=29012
Talk:Compiling for Android
2013-06-23T15:45:47Z
<p>Sorlok reaves: /* oops */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That definitely should be possible! However I see lots of little changes are needed. Have you got a Raspberry Pi? I'd love to see it happen.<br />
<br />
AFAIK android uses identical ABIs to other ELF unix OSes on the same CPU architectures (though I get the hint that there may be slight differences in the linker due to some platforms not following the standards to the letter).<br />
<br />
My changes to both FB and the OHR conflate Android and ARM in several places. You would want to compile both for a GNU/Linux target instead of Android (if you run a normal GNU/Linux distrib with ncurses and X11 on the RPi). SConscript needs several changes in addition to the ones I just made. <s>If SDL on the RPi uses the frame buffer, then you may not want to link with the X11 libs</s> (actually libfb still links to X11 by default, unless you add -DDISABLE_X11 to config.mk). And it'll need to know where to find the FB libs (it looks like the Raspberry Pi toolchain uses a platform triple of arm-bcm2708hardfp-linux-gnueabi, so you'll probably edit SConscript where I hardcoded arm-linux-androideabi). Looking through my changes to FB, it looks like the only thing which needs changing for GNU/Linux on ARM is to edit rtlib/static/fbrt0.c to use __attribute__((constructor)). fbc should actually be fine for cross compiling to any 32 bit target as long as it's invoked with "-arch arm" (well not quite true: it assumes that it knows how all structures are laid out in memory (that they are the same as laid out by GCC on x86... I'm surprised this hasn't blown up on ARM!)<br />
<br />
Look at compile-using-toolchain.sh for how to invoke scons (you'd build Game/Custom directly using scons). You'd probably also need to add the directory containing a build of libSDL.so (and libSDL_mixer.so (or use music=silence)) cross-compiled to the Raspberry Pi to the CXXLINKFLAGS variable in SConscript (we ought to set the standard LDFLAGS variable instead).<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've got one coming in the mail ---and I noticed you separated the Android/ARM targets in a recent commit, thanks! I'll give it a whirl once my device arrives.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool. I might get around to some other clean up before then. Those SConscript changes were just one thing needed.<br />
<br />
<br />
==Hacking my way through the RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): So I've started working on cross-compiling for Raspberry PI. I'm stuck on the "make rtlib" step of FreeBasic (I'll work through it more later, but I figured I've reached a stopping point for tonight so might as well document it.)<br />
<br />
First, I installed crosstools-ng, which is by far the most common suggestion for setting up a toolchain. I followed this post for the most part:<br />
http://www.kitware.com/blog/home/post/426<br />
<br />
Then, I ssh-ed into the RPi and installed libSDL1.2-dev, and all related libraries. Then I did this:<br />
cd ~/opt/rpi_sync<br />
rsync -rl pi@192.168.1.110:/usr .<br />
<br />
Following that, I checked out the FreeBasic repo listed above and used this config.mk file:<br />
ENABLE_PREFIX=1<br />
prefix=/home/sethhetu/opt/fbc-0.90-rpi<br />
PATH:=/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/bin:$(PATH)<br />
CFLAGS := -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf<br />
TARGET_PREFIX=arm-unknown-linux-gnueabi-<br />
V=1<br />
<br />
Note that CFLAGS; I was guessing that this would allow me to use the same headers on both the RPi and my laptop. This is just a guess!<br />
<br />
At that point, I ran:<br />
make compiler<br />
<br />
It ran to completion, then:<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
<br />
It fails with:<br />
arm-unknown-linux-gnueabi-gcc -Wall -Werror-implicit-function-declaration -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf -c src/rtlib/linux/sys_portio.c -o src/rtlib/arm-unknown-linux-gnueabi-obj/sys_portio.o<br />
src/rtlib/linux/sys_portio.c: In function 'fb_hIn':<br />
src/rtlib/linux/sys_portio.c:11:2: error: impossible constraint in 'asm'<br />
<br />
The line in question:<br />
__asm__ volatile ("inb %1, %0" : "=a" (value) : "d" (port));<br />
<br />
Here's where I'm stumped. If it was a header-file error, I wouldn't be surprised. But it's a c file, and involves the correct toolchain item (arm-unknown-linux-gnueabi-gcc). And the same FreeBasic repo compiled fine for Android with a similar config.mk (but no CFLAGS in this case).<br />
<br />
Anyway, I'll debug more this weekend. Any suggestions would be appreciated.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool.<br />
<br />
I hadn't noticed that there was asm in use there. To do things properly, we'll want to move that stuff into an x86-specific file, and also provide an ARM implementation of port IO. I assume that anyone using a RPi for hardware hacking and using FB will want ARM implementations of that. I don't know anything about that stuff. But it apears that ARM doesn't have separate IO instructions. Someone said:<br />
The ARM architecture doesn't specify a separate I/O space -- everything is memory-mapped, so:<br />
1) There are no dedicated (fast-path) I/O instructions -- load and store is all you get.<br />
For example see [http://elinux.org/RPi_Low-level_peripherals#C_2 here] for an example of accessing the RPi GPIO pins using memory mapping. Well, I'll ask on the FB forums, there are people there who care about that.<br />
<br />
Anyway, just comment out that assembly. If you look at rtlib/android/sys_portio.c you'll see the functions throw errors.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just adding this brief tidbit; the Android-compiled version has:<br />
<br />
arm-linux-androideabi-gcc -DENABLE_MT -Wall -Werror-implicit-function-declaration -DDISABLE_FFI -DDISABLE_X11 -DDISABLE_NCURSES -Wfatal-errors -O2 -c src/rtlib/android/sys_portio.c -o src/rtlib/arm-linux-androideabi-objmt/sys_portio.o<br />
<br />
...which contains:<br />
<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
...etc. I'm sure you (TMC) know about this; I'm just adding it for clarity's sake to anyone else reading along.<br />
<br />
No time to work on this today, but I'll probably use FB_RTERROR_ILLEGALFUNCTIONCALL in the linux/sys_portio.c when I get a chance.<br />
<br />
'''Edit:''' Whoops, I should really read the Talk page before posting; the previous post by TMC already has all of this. Thanks for the link on I/O mapping; it's informative!</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=29011
Talk:Compiling for Android
2013-06-23T15:43:04Z
<p>Sorlok reaves: /* Minor comment on how Android works around this. */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That definitely should be possible! However I see lots of little changes are needed. Have you got a Raspberry Pi? I'd love to see it happen.<br />
<br />
AFAIK android uses identical ABIs to other ELF unix OSes on the same CPU architectures (though I get the hint that there may be slight differences in the linker due to some platforms not following the standards to the letter).<br />
<br />
My changes to both FB and the OHR conflate Android and ARM in several places. You would want to compile both for a GNU/Linux target instead of Android (if you run a normal GNU/Linux distrib with ncurses and X11 on the RPi). SConscript needs several changes in addition to the ones I just made. <s>If SDL on the RPi uses the frame buffer, then you may not want to link with the X11 libs</s> (actually libfb still links to X11 by default, unless you add -DDISABLE_X11 to config.mk). And it'll need to know where to find the FB libs (it looks like the Raspberry Pi toolchain uses a platform triple of arm-bcm2708hardfp-linux-gnueabi, so you'll probably edit SConscript where I hardcoded arm-linux-androideabi). Looking through my changes to FB, it looks like the only thing which needs changing for GNU/Linux on ARM is to edit rtlib/static/fbrt0.c to use __attribute__((constructor)). fbc should actually be fine for cross compiling to any 32 bit target as long as it's invoked with "-arch arm" (well not quite true: it assumes that it knows how all structures are laid out in memory (that they are the same as laid out by GCC on x86... I'm surprised this hasn't blown up on ARM!)<br />
<br />
Look at compile-using-toolchain.sh for how to invoke scons (you'd build Game/Custom directly using scons). You'd probably also need to add the directory containing a build of libSDL.so (and libSDL_mixer.so (or use music=silence)) cross-compiled to the Raspberry Pi to the CXXLINKFLAGS variable in SConscript (we ought to set the standard LDFLAGS variable instead).<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've got one coming in the mail ---and I noticed you separated the Android/ARM targets in a recent commit, thanks! I'll give it a whirl once my device arrives.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool. I might get around to some other clean up before then. Those SConscript changes were just one thing needed.<br />
<br />
<br />
==Hacking my way through the RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): So I've started working on cross-compiling for Raspberry PI. I'm stuck on the "make rtlib" step of FreeBasic (I'll work through it more later, but I figured I've reached a stopping point for tonight so might as well document it.)<br />
<br />
First, I installed crosstools-ng, which is by far the most common suggestion for setting up a toolchain. I followed this post for the most part:<br />
http://www.kitware.com/blog/home/post/426<br />
<br />
Then, I ssh-ed into the RPi and installed libSDL1.2-dev, and all related libraries. Then I did this:<br />
cd ~/opt/rpi_sync<br />
rsync -rl pi@192.168.1.110:/usr .<br />
<br />
Following that, I checked out the FreeBasic repo listed above and used this config.mk file:<br />
ENABLE_PREFIX=1<br />
prefix=/home/sethhetu/opt/fbc-0.90-rpi<br />
PATH:=/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/bin:$(PATH)<br />
CFLAGS := -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf<br />
TARGET_PREFIX=arm-unknown-linux-gnueabi-<br />
V=1<br />
<br />
Note that CFLAGS; I was guessing that this would allow me to use the same headers on both the RPi and my laptop. This is just a guess!<br />
<br />
At that point, I ran:<br />
make compiler<br />
<br />
It ran to completion, then:<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
<br />
It fails with:<br />
arm-unknown-linux-gnueabi-gcc -Wall -Werror-implicit-function-declaration -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf -c src/rtlib/linux/sys_portio.c -o src/rtlib/arm-unknown-linux-gnueabi-obj/sys_portio.o<br />
src/rtlib/linux/sys_portio.c: In function 'fb_hIn':<br />
src/rtlib/linux/sys_portio.c:11:2: error: impossible constraint in 'asm'<br />
<br />
The line in question:<br />
__asm__ volatile ("inb %1, %0" : "=a" (value) : "d" (port));<br />
<br />
Here's where I'm stumped. If it was a header-file error, I wouldn't be surprised. But it's a c file, and involves the correct toolchain item (arm-unknown-linux-gnueabi-gcc). And the same FreeBasic repo compiled fine for Android with a similar config.mk (but no CFLAGS in this case).<br />
<br />
Anyway, I'll debug more this weekend. Any suggestions would be appreciated.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool.<br />
<br />
I hadn't noticed that there was asm in use there. To do things properly, we'll want to move that stuff into an x86-specific file, and also provide an ARM implementation of port IO. I assume that anyone using a RPi for hardware hacking and using FB will want ARM implementations of that. I don't know anything about that stuff. But it apears that ARM doesn't have separate IO instructions. Someone said:<br />
The ARM architecture doesn't specify a separate I/O space -- everything is memory-mapped, so:<br />
1) There are no dedicated (fast-path) I/O instructions -- load and store is all you get.<br />
For example see [http://elinux.org/RPi_Low-level_peripherals#C_2 here] for an example of accessing the RPi GPIO pins using memory mapping. Well, I'll ask on the FB forums, there are people there who care about that.<br />
<br />
Anyway, just comment out that assembly. If you look at rtlib/android/sys_portio.c you'll see the functions throw errors.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just adding this brief tidbit; the Android-compiled version has:<br />
<br />
arm-linux-androideabi-gcc -DENABLE_MT -Wall -Werror-implicit-function-declaration -DDISABLE_FFI -DDISABLE_X11 -DDISABLE_NCURSES -Wfatal-errors -O2 -c src/rtlib/android/sys_portio.c -o src/rtlib/arm-linux-androideabi-objmt/sys_portio.o<br />
<br />
...which contains:<br />
<br />
int fb_hIn( unsigned short port ) {<br />
return fb_ErrorSetNum( FB_RTERROR_ILLEGALFUNCTIONCALL );<br />
}<br />
<br />
...etc. I'm sure you (TMC) know about this; I'm just adding it for clarity's sake to anyone else reading along.<br />
<br />
No time to work on this today, but I'll probably use FB_RTERROR_ILLEGALFUNCTIONCALL in the linux/sys_portio.c when I get a chance.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=29007
Talk:Compiling for Android
2013-06-21T00:53:53Z
<p>Sorlok reaves: /* Raspberry Pi try 1: Failure */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That definitely should be possible! However I see lots of little changes are needed. Have you got a Raspberry Pi? I'd love to see it happen.<br />
<br />
AFAIK android uses identical ABIs to other ELF unix OSes on the same CPU architectures (though I get the hint that there may be slight differences in the linker due to some platforms not following the standards to the letter).<br />
<br />
My changes to both FB and the OHR conflate Android and ARM in several places. You would want to compile both for a GNU/Linux target instead of Android (if you run a normal GNU/Linux distrib with ncurses and X11 on the RPi). SConscript needs several changes in addition to the ones I just made. <s>If SDL on the RPi uses the frame buffer, then you may not want to link with the X11 libs</s> (actually libfb still links to X11 by default, unless you add -DDISABLE_X11 to config.mk). And it'll need to know where to find the FB libs (it looks like the Raspberry Pi toolchain uses a platform triple of arm-bcm2708hardfp-linux-gnueabi, so you'll probably edit SConscript where I hardcoded arm-linux-androideabi). Looking through my changes to FB, it looks like the only thing which needs changing for GNU/Linux on ARM is to edit rtlib/static/fbrt0.c to use __attribute__((constructor)). fbc should actually be fine for cross compiling to any 32 bit target as long as it's invoked with "-arch arm" (well not quite true: it assumes that it knows how all structures are laid out in memory (that they are the same as laid out by GCC on x86... I'm surprised this hasn't blown up on ARM!)<br />
<br />
Look at compile-using-toolchain.sh for how to invoke scons (you'd build Game/Custom directly using scons). You'd probably also need to add the directory containing a build of libSDL.so (and libSDL_mixer.so (or use music=silence)) cross-compiled to the Raspberry Pi to the CXXLINKFLAGS variable in SConscript (we ought to set the standard LDFLAGS variable instead).<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've got one coming in the mail ---and I noticed you separated the Android/ARM targets in a recent commit, thanks! I'll give it a whirl once my device arrives.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Cool. I might get around to some other clean up before then. Those SConscript changes were just one thing needed.<br />
<br />
<br />
==Hacking my way through the RPi==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): So I've started working on cross-compiling for Raspberry PI. I'm stuck on the "make rtlib" step of FreeBasic (I'll work through it more later, but I figured I've reached a stopping point for tonight so might as well document it.)<br />
<br />
First, I installed crosstools-ng, which is by far the most common suggestion for setting up a toolchain. I followed this post for the most part:<br />
http://www.kitware.com/blog/home/post/426<br />
<br />
Then, I ssh-ed into the RPi and installed libSDL1.2-dev, and all related libraries. Then I did this:<br />
cd ~/opt/rpi_sync<br />
rsync -rl pi@192.168.1.110:/usr .<br />
<br />
Following that, I checked out the FreeBasic repo listed above and used this config.mk file:<br />
ENABLE_PREFIX=1<br />
prefix=/home/sethhetu/opt/fbc-0.90-rpi<br />
PATH:=/home/sethhetu/opt/x-tools/arm-unknown-linux-gnueabi/bin:$(PATH)<br />
CFLAGS := -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf<br />
TARGET_PREFIX=arm-unknown-linux-gnueabi-<br />
V=1<br />
<br />
Note that CFLAGS; I was guessing that this would allow me to use the same headers on both the RPi and my laptop. This is just a guess!<br />
<br />
At that point, I ran:<br />
make compiler<br />
<br />
It ran to completion, then:<br />
make rtlib TARGET=arm-unknown-linux-gnueabi<br />
<br />
It fails with:<br />
arm-unknown-linux-gnueabi-gcc -Wall -Werror-implicit-function-declaration -I/home/sethhetu/opt/rpi_sync/usr/include -I/home/sethhetu/opt/rpi_sync/usr/include/arm-linux-gnueabihf -c src/rtlib/linux/sys_portio.c -o src/rtlib/arm-unknown-linux-gnueabi-obj/sys_portio.o<br />
src/rtlib/linux/sys_portio.c: In function 'fb_hIn':<br />
src/rtlib/linux/sys_portio.c:11:2: error: impossible constraint in 'asm'<br />
<br />
The line in question:<br />
__asm__ volatile ("inb %1, %0" : "=a" (value) : "d" (port));<br />
<br />
Here's where I'm stumped. If it was a header-file error, I wouldn't be surprised. But it's a c file, and involves the correct toolchain item (arm-unknown-linux-gnueabi-gcc). And the same FreeBasic repo compiled fine for Android with a similar config.mk (but no CFLAGS in this case).<br />
<br />
Anyway, I'll debug more this weekend. Any suggestions would be appreciated.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=28995
Talk:Compiling for Android
2013-06-11T08:58:12Z
<p>Sorlok reaves: /* Speculation */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That definitely should be possible! However I see lots of little changes are needed. Have you got a Raspberry Pi? I'd love to see it happen.<br />
<br />
AFAIK android uses identical ABIs to other ELF unix OSes on the same CPU architectures (though I get the hint that there may be slight differences in the linker due to some platforms not following the standards to the letter).<br />
<br />
My changes to both FB and the OHR conflate Android and ARM in several places. You would want to compile both for a GNU/Linux target instead of Android (if you run a normal GNU/Linux distrib with ncurses and X11 on the RPi). SConscript needs several changes in addition to the ones I just made. <s>If SDL on the RPi uses the frame buffer, then you may not want to link with the X11 libs</s> (actually libfb still links to X11 by default, unless you add -DDISABLE_X11 to config.mk). And it'll need to know where to find the FB libs (it looks like the Raspberry Pi toolchain uses a platform triple of arm-bcm2708hardfp-linux-gnueabi, so you'll probably edit SConscript where I hardcoded arm-linux-androideabi). Looking through my changes to FB, it looks like the only thing which needs changing for GNU/Linux on ARM is to edit rtlib/static/fbrt0.c to use __attribute__((constructor)). fbc should actually be fine for cross compiling to any 32 bit target as long as it's invoked with "-arch arm" (well not quite true: it assumes that it knows how all structures are laid out in memory (that they are the same as laid out by GCC on x86... I'm surprised this hasn't blown up on ARM!)<br />
<br />
Look at compile-using-toolchain.sh for how to invoke scons (you'd build Game/Custom directly using scons). You'd probably also need to add the directory containing a build of libSDL.so (and libSDL_mixer.so (or use music=silence)) cross-compiled to the Raspberry Pi to the CXXLINKFLAGS variable in SConscript (we ought to set the standard LDFLAGS variable instead).<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I've got one coming in the mail ---and I noticed you separated the Android/ARM targets in a recent commit, thanks! I'll give it a whirl once my device arrives.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=28992
Talk:Compiling for Android
2013-06-10T13:57:13Z
<p>Sorlok reaves: /* wild, irresponsible speculation. */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:17, 4 June 2013 (PDT): Oh! Duh! I did have a change that I had not realized. I had forgotten that I was messing around with CompatibilityHacksAdditionalPreloadedSharedLibraries in my AndroidAppSettings.cfg<br />
<br />
Although now I am confused by other problems. I saw the same "undefined reference to 'SDL_main" error, but when I messed with my symlinks I seem to have screwed up everything, and compile-using-toolchain.sh doesn't seem to create any files in android/tmp I am going to start over.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:53, 4 June 2013 (PDT): Yay! I followed the instructions more carefully, and now I can build a working apk! I had failed to notice the difference between scons android=1 and scons android-source=1<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Great :). Were you trying to preload libapplication.so? I could see that leading to problems, though I still don't understand how the error message could have been caused.<br />
<br />
The files in android/tmp are created by android_source_files in ohrbuild.py, which really ought to be replaced with a better system. Either calling the standalone toolchain from scons, or continuing with a two phase build with our own Android.mk to replace the unsuitable one in the SDL port.<br />
<br />
Just like on all other platforms, unless you compile with debug=0 (which we do for Windows nightly builds but not Mac or Linux), when FB encounters an error it will immediately kill the program (I really ought to install a hook in the rtlib to handle this better!). That includes when OPEN fails, which is pretty likely on Android because of all the paths that need updating.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).<br />
<br />
<br />
==Speculation==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): This is just wild speculation, but it occurs to me that the Raspberry Pi uses an ARM chip and is known to run SDL apps. Do you think the same basic steps here would work for that? (Obviously I would have to create my own toolchain instead of using the NDK's version.) In particular, I'm wondering if, given a properly configured toolchain, FB '''should''' be able to "make compiler" and "make rtlib". Any thoughts?</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=28982
Talk:Compiling for Android
2013-06-04T14:07:17Z
<p>Sorlok reaves: /* Symlinks */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
==debuginfo crash==<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Having another look at the generated C and looking through the rtlib source, that error message still looks impossible. There is no place where line number 352 is passed to anything, unless its line 352 in a different file. Maybe you changed something and forgot to recompile using scons before invoking build.sh? Also once scons stopped because of an error which I failed to notice but build.sh still built successfully, which was quite confusing.<br />
<br />
I suggest adding "sleep(3000)" at the top of debuginfo to allow gdb time to attach. The ndk-gdb script contains a wait of several seconds.<br />
<br />
==Ubuntu==<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Right, that's at the "make compiler" step. The fbc compiler itself is a 32 bit binary (although there is a 64 bit branch of FB, which didn't yet work last time I checked). The rtlib of course shouldn't depend on anything outside the standalone toolchain.<br />
<br />
==Symlinks==<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): Yeah, I found the arguments to ln to be quite unintuitive. ln and ln -s work quite differently: ln (for hardlinks) takes a normal file path, while ln -s takes a string which is put into the symlink and is relative to the symlink's location, not the current directory. Some versions of GNU ln takes an -r argument (relative) to make the ln -s destination relative to the current directory instead.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I would recommend that new developers generally avoid hard symlinks; otherwise, a stray "rm" command can take out the OHR and Free Basic directories as well. At least for the '''last''' step (linking the current SDL project to "src") you should definitely use a soft link unless you're 100% sure you want a hard link. (Note that I used only soft links for compiling).</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=28979
Talk:Compiling for Android
2013-06-03T17:38:06Z
<p>Sorlok reaves: **grumble, wiki syntax, grumble***</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=28978
Talk:Compiling for Android
2013-06-03T17:36:13Z
<p>Sorlok reaves: /* figured it out */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Ok, I figured it out. The wiki text "Create a symlink from sdl-android/project/jni/application/src to sdl-android/project/jni/application/ohrrpgce" was somewhat inaccurate. You actually want to do something like this: <br />
<br />
<pre><br />
cd sdl-android<br />
ln -s ohrrpgce project/jni/application/src<br />
</pre><br />
<br />
...because build.sh expects the link to point '''locally''' to "ohrrpgce". If you point it to anything else (subfolder, absolute path, etc.) it will fail.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=28977
Talk:Compiling for Android
2013-06-03T17:02:46Z
<p>Sorlok reaves: /* Compile error. */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): I'm getting "undefined reference to 'SDL_main'" when I try to run ./build.sh inside the sdl-android directory. Is anyone else getting this?<br />
<br />
http://pastie.org/8001219</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:Compiling_for_Android&diff=28976
Talk:Compiling for Android
2013-06-03T16:12:39Z
<p>Sorlok reaves: /* Minor issue I ran into */</p>
<hr />
<div>[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:01, 1 June 2013 (PDT): I am a bit confused by the freebasic instructions. I understand that I need to create a config.mk and put some stuff in it. I don't know what the toolchain is, except that the android/compile-using-toolchain.sh script seems to populate it, but I have not yet gotten to the step where I try that. Why /tmp ? That seems like a really strange place to put anything.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 22:09, 1 June 2013 (PDT): My config.mk looks like this:<br />
<br />
<pre>ENABLE_PREFIX=1<br />
prefix=/home/james/misc/fbc-0.90-android<br />
PATH:=/tmp/android-toolchain/bin:$(PATH) <br />
TARGET_PREFIX=arm-linux-androideabi-</pre><br />
<br />
but when I do make compiler it says:<br />
<pre>james@rhinoctopus:~/src/misc/fbc-tmc$ make compiler<br />
makefile:414: target `src/rtlib/arm-linux-androideabi-' given more than once in the same rule.<br />
makefile:414: target `obj' given more than once in the same rule.<br />
makefile:414: target `freebasic' given more than once in the same rule.<br />
makefile:445: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:417: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:445: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:448: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:459: warning: overriding commands for target `lib/arm-linux-androideabi-'<br />
makefile:451: warning: ignoring old commands for target `lib/arm-linux-androideabi-'<br />
makefile:462: *** multiple target patterns. Stop.</pre><br />
<br />
I assume this is just because I don't know jack about makefiles :)<br />
<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): That was quite puzzling, but I've discovered that it must be because you have a space at the end of the TARGET_PREFIX line. I sure hate makefiles.<br />
<br />
The standalone NDK toolchain is the bare essentials copied from the NDK: tools, headers and libraries to target a single specific arch+android version using a specific GCC/Clang version. I simply put it in /tmp because that's what the NDK docs suggested. Probably because you install multiple of them for your different targets then can delete when done. My own config.mk:<br />
<br />
<pre><br />
PATH:=/tmp/android-toolchain/bin:$(PATH)<br />
TARGET_PREFIX=arm-linux-androideabi-<br />
<br />
ENABLE_PREFIX=1<br />
CFLAGS := -Wfatal-errors -O0 -g<br />
prefix=/home/ralph/local/fbc-0.90-android<br />
V=1<br />
</pre><br />
<br />
Looking closer at the makefile, I see $prefix is the install location, so you definitely want to specify that, since it's not a normal build of FB; though you could build non-crosscompiling versions of rtlib and fbgfx libraries and install those too and it should work<br />
<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 12:26, 2 June 2013 (PDT): Yup! You are right. That one extra space was throwing it off.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 13:04, 2 June 2013 (PDT): I was able to get through the whole compiling process, and ended up with an apk, but when I ran it on the device, it crashed with a file-not-found in debuginfo http://pastebin.com/raw.php?i=gxyATZTg<br />
<br />
[[User:TMC|TMC]] ([[User talk:TMC|talk]]): What is line 352 in common.rbas.bas for you? For me, with no modifications to common.rbas, it is "sizeerror = -1". I don't see which line in debuginfo could throw that error, unless it's possible for a "file not found" error to be printed when attempting to access a file which has already been successfully opened...<br />
<br />
You could try using ndk-gdb (instructions on [[Android Port]] to see where exactly in libfb the error is thrown from. Also it's a mystery how debuginfo is being called before startup, so the backtrace will be very interesting.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] ([[User talk:Bob the Hamster|talk]]) 07:29, 3 June 2013 (PDT): Line 352 in common.rbas.bas is the same for me as for you. Nothing looks unusual in that file. I tried ndk-debug but the SDL logo flickers on the device's screen and the crashes before gdb has a chance to connect to it: http://pastebin.com/raw.php?i=BcEzMxKu<br />
<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]] ([[User talk:Sorlok reaves|talk]]): Just thought I might add that, for Ubuntu users, Free Basic won't compile unless you install '''lib32ncurses5-dev'''. I'm not sure why Free Basic complained about this particular library; for everything else, "libWhatever-dev" seemed to work.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=What_languages_do_you_speak%3F&diff=25351
What languages do you speak?
2010-06-30T14:41:30Z
<p>Sorlok reaves: /* Bahasa Malaysia didn't pan out; switched to Burmese. Thought this page should at least be kept up to date. :D */</p>
<hr />
<div>__NOTOC__<br />
=English=<br />
The [[OHRRPGCE]] development team speaks mostly english, and at the moment, only an english language version of the OHRRPGCE is available.<br />
<br />
=Other Languages=<br />
Many users of the OHRRPGCE speak other languages, and many speak english only as a second language. There are even some OHRRPGCE users who do not speak english at all.<br />
<br />
==Brazilian Portugese==<br />
[[User:Ohr.brazilian.traductor|Thiago Bayerlein]] has provided [[Documentation#Documentation in Other Languages|translations]] of much of the OHRRPGCE's documentation in Brazilian Portugese.<br />
<br />
==Esperanto==<br />
[[James Paige]] speaks a little [[wikipedia:Esperanto|Esperanto]], and occasionally translates the [[News]] page... although he hasn't been doing it lately.<br />
<br />
==Ido==<br />
'''Arpgme''': Is currently learning [[wikipedia:Ido|Ido]] (an improvement of Esperanto intended to be 4 times easier than Esperanto). He finnaly became the translation. <br />
<br />
[[Ido:Ohrrpgce En Ido|Ido Translation]]<br />
<br />
I would do more but it would take forever to translate all of the documents and it wouldn't make sence to have the douments and no Ohrrpgce. That is why I plan to translate OHR into Ido before I continue.<br />
<br />
==French==<br />
[[User:Pkmnfrk|Mike Caron]] speaks a bit of French, although he would have a hard time keeping up a conversation. In spite of this, his French is not beyond ordering a burger at McDonalds in Montréal. He has no plans whatsoever to translate anything into it.<br />
<br />
==ä¸æ–‡/မြန်မာစကား==<br />
[[User:Sorlok reaves | Seth]] can get around in Chinese. He speaks a small amount (and can write a much larger amount) of Burmese. <br />
<br />
<br />
[[Category:FAQ]]</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=History_of_Release_Dates&diff=25314
History of Release Dates
2010-06-15T16:42:34Z
<p>Sorlok reaves: /* Added Zenzizenzic as the mystery Z* named release */</p>
<hr />
<div>New official stable versions of the OHRRPGCE are released on a when-we-feel-like-it schedule. Each version is given a code-name for reference.<br />
<br />
{| border="1" cellpadding="5"<br />
! version || codename || release date || notes<br />
|- <br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[handshake]] || Nov 29 2002 ||<br />
|- <br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[espereble]] || July 31 2003 ||<br />
|- <br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[wolfwood]] || August 04 2003 ||<br />
|- <br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[paternoster]] || October 05 2003 ||<br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[ozarks]] || June 28 2004 || <br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[quaternion]] || May 19 2005 || First [[GPL license]] version<br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[rusalka]] || October 03 2005 || <br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[serendipity]] || February 15 (16+) 2006 || First official [[FreeBasic]] (Windows/Linux) version, midi music format support.<br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[tirgoviste]] || March 15 2006 ||<br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[hasta-la-qb]] || July 09 (Aug 03+) 2006 || Final DOS/[[QuickBasic]] release<br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[ubersetzung]] || September 21 2007 || Includes a new demo game "Vikings of Midgard"<br>Sound effects, more music formats, 32-bit plotscripting, map layers<br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[voxhumana]] || January 21 2008 || Complete menu, UI colours customisation<br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[werewaffle]] || August 16 (20+) 2008 || default master palette replacement, big performance improvements<br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[xocolatl]] || October 2 (3+) 2008 || Portraits, box borders, NPC limit increase. xocolatl+ release includes an update to Vikings of Midgard that uses the new portrait feature<br />
|-<br />
| [http://HamsterRepublic.com/ohrrpgce/archive/ previous] || [[xocolatl+2]] || April 3 2009 || Loads of bugfixes, no new features<br />
|-<br />
| [[Downloads|current]] || [[ypsiliform]] || Jan 8 / Jan 28(+1) / Feb 8(+2) 2010 || Integrated F1 help, more map layers, slice-tree-infrastructure, Item limit increase, Script trigger maintenance tools, sprite import/export, DirectX backend for Windows, Default backend is now SDL, tons of other features and fixes<br />
|-<br />
| next || zenzizenzic || to be determined || Some of the things in the [[Plans]] list, and who knows what else?<br />
|-<br />
| future || [[yggdrasil]] || to be determined || Special stable release to coincide with completion of [[Game:Vikings of Midgard|Vikings of Midgard]]. May come before or after Z-release depending on scheduling.<br />
|}<br />
<br />
<br />
<br />
=See Also=<br />
<br />
* [[Release tracking bug]]<br />
* [[Source]]<br />
<br />
[[Category:Articles that change at each release]]</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=OHRRPGCE_Source_Ports&diff=24612
OHRRPGCE Source Ports
2009-10-23T00:42:42Z
<p>Sorlok reaves: /* Moving to dormant */</p>
<hr />
<div>This page summarizes altered versions of OHRRPGCE, and also total rewrites.<br />
'port' means to make something work in a different system.<br />
This usually refers to OS or language, but can also refer to programming-library.<br />
<br />
=Active Projects=<br />
<br />
== nohrio ==<br />
* Not a source port, but [[nohrio]] is a python library for working with the data in RPG file lumps.<br />
<br />
== fb2cpp ==<br />
The fb2cpp project (no web site yet) is a tool intended to convert FreeBasic source code into C++ syntax so that it can be compiled by gcc.<br />
<br />
=Dormant Projects=<br />
<br />
== OHRRPGCE FMF ==<br />
* Put on hold indefinitely, possibly canceled (see developer's list email).<br />
*[[OHRRPGCEFMF | Developer's Wiki]]<br />
* Full re-write of GAME.EXE in Java, using pure J2ME (java for mobile phones.) <br />
* [http://code.google.com/p/ohrrpgce-fmf/ (Alpha) Prima Facie] is available; a power-crazed menu system threatens to conquer the world. Fortunately, battles are easy.<br />
* Intended to be developed in parallel with the Freebasic version, '''not''' as a replacement.<br />
* Currently (Stage I) Progress: <br />
{{meter|27|150|15|red|gray|Alpha|Beta}}<br />
[[Image:Fmf_dopod.jpg]]<br />
<br />
== OHR++ ==<br />
* Details: [[OHR++]]<br />
* C++ rewrite.<br />
* <s>In heavy development.</s><br />
* Source documentation at http://www.castleparadox.com/ohr++/<br />
* Read-only subversion repository at http://svn.castleparadox.com/repos/ohr++/<br />
* Windows Demo (Updated periodically): http://www.castleparadox.com/ohr++.zip<br />
** Drag an .RPG file into the executable after unzipping.<br />
<br />
== OHR-2 ==<br />
* Details: Nothing known<br />
* C + SDL rewrite.<br />
* No news about project or developer for a couple of years, most probably dead<br />
* No subversion repository?<br />
<br />
= Dead Projects =<br />
<br />
== Jormungand ==<br />
* Details: [[Jormungand]]<br />
* Python port.<br />
* Aims to make OHRRPGCE much easier to hack on and much more portable.<br />
* [http://dgowers-tech.livejournal.com/8109.html Development halted]<br />
* Read-only subversion access via svn://gilgamesh.HamsterRepublic.com/jormungand<br />
<br />
<br />
== Battle-engine-free GAME.EXE ==<br />
* A [[QuickBasic]] version of GAME.EXE trading battle support for enlarged plotscripting buffer and more globals, which will soon become standard.<br />
* Incompatible [[SAV]] files stored the extra 7000 globals.<br />
* Last version based on [[Quaternion]]<br />
* Pretty useless [http://www.freewebs.com/gwp_/battleless.zip executable] and [http://www.freewebs.com/gwp_/battleless%5Fsource.zip source] available.<br />
* [[User:The Mad Cacti|The Mad Cacti]] made 1 or 2 games using this branch, and it was used for [http://www.castleparadox.com/gamelist-display.php?game=329 Ziggurats for Red Turtle] by Rinku<br />
<br />
=Merged Projects=<br />
<br />
== FBOHR ==<br />
* Details: [[FBOHR]] (merged/replaced original [[OHRRPGCE]] development)<br />
* Direct [[FreeBASIC]] port.<br />
* Aims to create a full-featured 32-bit port of the OHR, then add features.<br />
* Nearly complete (based on the goal of being identical to the OHR)<br />
* Source files have been integrated into the OHRRPGCE project, so if you have the OHR source, you have FBOHR's source too.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=FMF:The_HVM,_From_the_Bottom_Up&diff=22314
FMF:The HVM, From the Bottom Up
2009-02-03T10:01:17Z
<p>Sorlok reaves: /* else/end are both required. */</p>
<hr />
<div>{{FMF}}<br />
<br />
<br />
This page describes the workings of the HVM in relation to the bytecodes it processes. For your reference, here is a copy of the data structures diagram:<br />
<br />
<br />
[[File:Fmf hvm data structures.png]]<br />
<br />
<br />
We will now list some critical operations performed by the HVM and how it uses the data structures to track these operations. Before reading this section, you should probably read [[Henceforth_VM#Henceforth Syntax Dictionary | the syntax dictionary]], so that you have an idea of Henceforth's intended functionality. Otherwise, this section might not make a lot of sense.<br />
<br />
For these charts, the green background is default. If the background is red, that means the chart only applies to a full-blown user script. A subroutine will fail, since it doesn't have the requisite data structures allocated. We check if a subroutine is being run by seeing if the PC Stack's size is greater than 1. Checking another stack's size to determine this is non-standard. <br />
<br />
For brevity, when we say "advance the PC by one" (or, equivalently, PC += 1), we are referring to a "smart increment". For multi-word bytecodes, this means we have to increment by the length of the bytecode, not simply by 1.<br />
<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#C8FFC8" colspan=2 |'''''Lifecycle'''''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
After the HVM initializes, and before it starts running scripts.<br />
| valign="top" | '''Response'''<br />
* Create a new entry in the script cache<br />
** New Name = "root"<br />
** ID = -1<br />
** Pinned = true<br />
** In Use = 1<br />
** Bytecode = new Array[]<br />
** DoTable = new Array()<br />
** Optionally, load into "Bytecode" the contents of "global.HF", or read directly from user input.<br />
** '''Note:''' We do not create an entry in the script name multiplexer; root is effectively read-only.<br />
* Make a new Script UDT:<br />
** Local Stack = new Stack()<br />
** Program Counter Stack = new Stack(0)<br />
** Script Cache Pointer Stack = new Stack(Script Cache Reference)<br />
** Do/If Stack Stack = new Stack(new Stack())<br />
** Local Variable Hash Stack = new Stack(new Hash())<br />
** ID = -1<br />
** Parent Script Pointer = -1<br />
** Local Variable Array = new Array()<br />
** String Table = new Array()<br />
** Local Function Start Indices = new Array()<br />
** Return Address Stack = new Stack()<br />
** '''Note:''' If the root script attempts to use script-only data structures (e.g., local parameters), the behavior of the HVM is undefined.<br />
* Make a new entry in the Running Scripts table:<br />
** Script = our new Script UDT<br />
** Wait = false<br />
** Wait On = 0<br />
* Insert this new Running Scripts entry at index zero (or as the head node, if it's a linked list). This spot is reserved for the root script.<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all"<br />
|bgcolor="#C8FFC8" colspan=2 |'''''Functional Primitives'''''<br />
|-<br />
| '''Trigger'''<br />
dup<br />
<br>swap<br />
<br>drop<br />
<br>over<br />
<br>rot<br />
<br>add<br />
<br>sub<br />
<br>mult<br />
<br>div<br />
<br>random<br />
<br>b_xor<br />
<br>b_and<br />
<br>eq<br />
<br>lt<br />
<br>not<br />
<br>and<br />
<br>xor<br />
<br>b_not<br />
<br>b_or<br />
<br>or<br />
| valign="top" |'''Response'''<br />
* Pop a pre-defined number of arguments from the local stack.<br />
* Perform the desired math/logic<br />
* Push the resultant, if applicable, to the local stack.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#C8FFC8" colspan=2 |'''''Numbers'''''<br />
|-<br />
| '''Trigger'''<br />
The HVM encounters a "short integer" or a "full-width integer"<br />
| '''Response'''<br />
Push that number to the "local stack" of the currently-running script.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#FF6D6D" colspan=2 |'''''Script-Local Subroutines'''''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "define local script" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* In the "local function start indices" array, at index '''x''', insert (PC+1)<br />
* Advance the PC without executing commands until you reach an "end define".<br />
* Advance the PC by one.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "call local script" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* Push (PC+1) to the "return address stack"<br />
* Set the PC to the "local function start indices" array value at index '''x'''.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters an "end define" and the "return address stack" is not empty.<br />
| valign="top" | '''Response'''<br />
* Pop the "return address stack" and set the PC to this value.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#FF6D6D" colspan=2 |'''''Global Variables'''''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "retrieve global variable" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* If '''x''' is equal to the magic number (0x3FF for 10-bit ids), then pop the stack and store that value in '''x'''.<br />
* Push the value of that variable from the Global Variable Table.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "set global variable" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* Pop the stack.<br />
* If '''x''' is equal to the magic number (0x3FF for 10-bit ids), then pop the stack and store that value in '''x'''.<br />
* Store the popped value at location '''x''' in the Global Variable Table.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#FFFFFF" colspan=2 |'''''Call Subroutines'''''<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM, while running a script with id '''y''' in the Running Scripts Table, encounters a "call user-defined function by id" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* Retrieve a new entry in the script cache<br />
** Get entry '''x''' from the script lookup table; if "Cache" == -1, then load the source from the appropriate "Chunk" and add a new entry to the script_cache:<br />
*** New Name = null<br />
*** ID = '''x'''<br />
*** Pinned = false<br />
*** In Use = 0<br />
*** Bytecode = loadFromChunk()<br />
*** Do Table = new Array[do_udt]<br />
** For the appropriate script cache entry, set InUse = InUse + 1<br />
* Make a new Script UDT:<br />
** Local Stack = new Stack[]<br />
** Program Counter Stack = new Stack(0)<br />
** Script Cache Pointer = new Stack(id of the script cache entry we retrieved)<br />
** Do/If Stack Stack = new Stack(new Stack())<br />
** Local Variable Hash Stack = new Stack(new Hash{})<br />
** '''''*Note:''''' The HVM can leave "optional" components like this un-initialized until they are actually used.<br />
** ID = x<br />
** Parent Script Pointer = y<br />
** Local Variable Array = new Array[max+1]<br />
** String Table = new Array[]'''''*'''''<br />
** Local Function Start Indices = new Array[]'''''*'''''<br />
** Return Address Stack = new Stack[]<br />
** Set Local Variable Array to -1 at index 0<br />
* Make a new entry in the Running Scripts table:<br />
** Script = our new Script UDT<br />
** Wait = false<br />
** Wait On = 0<br />
* Insert this new Running Scripts entry one step ahead of script Y; this ensures it will be run next. <br />
* Set script Y's "Wait" flag to "true".<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM, while running a script with id '''y''' in the Running Scripts Table, encounters a "call named subroutine" with name '''exp'''.<br />
| valign="top" | '''Response'''<br />
* Retrieve a new entry in the script cache<br />
** Get entry '''exp''' from the script name multiplexer<br />
** Use '''exp''' to get the appropriate script cache entry; set its InUse = InUse + 1<br />
* In the current script UDT:<br />
** Program Counter Stack.push(0)<br />
** Script Cache Pointer.push(id of the script cache entry we retrieved)<br />
** Do/If Stack Stack.push(new Stack())<br />
** Local Variable Hash Stack.push(new Hash{})<br />
** '''''*Note:''''' The HVM can leave "optional" components like this un-initialized until they are actually used.<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM encounters a "hamsterspeak api call" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* Pop the agreed-upon number of arguments<br />
* Call API function '''x''' with said arguments<br />
* Push the return variable, if applicable<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#FFFFFF" colspan=2 |'''''Local Variables'''''<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM encounters a "push local variable by id" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* Pop the stack<br />
* If '''x''' is equal to the magic number (0x1FF for 9-bit ids), then pop the stack and store that value in '''x'''. Subtract 1 from x.<br />
* Store the popped value in script_udt.local_variable_array[x+1]<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM encounters a "pop local variable by id" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* If '''x''' is equal to the magic number (0x1FF for 9-bit ids), then pop the stack and store that value in '''x'''. Subtract 1 from x.<br />
* Retrieve the value in script_udt.local_variable_array[x+1]<br />
* Push the retrieved value to the stack<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM encounters a "pop local variable by name" with name '''test'''.<br />
| valign="top" | '''Response'''<br />
* Pop the stack<br />
* Create a key in script_udt.local_variable_hash_stack.top() for "test"<br />
* Store the popped value in script_udt.local_variable_hash_stack.top(){"test"}<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM encounters a "push local variable by name" with name '''test'''.<br />
| valign="top" | '''Response'''<br />
* If the local variable hash stack's top entry has no key at "test", push -1 and return<br />
* Retrieve the value in script_udt.local_variable_hash_stack.top(){"test"}<br />
* Push the retrieved value to the stack<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#C8FFC8" colspan=2 |'''''If Statements'''''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters an "if_start"<br />
| valign="top" | '''Response'''<br />
* Push a new do_udt with startIndex = PC to the do/if stack stack.top()<br />
* Retrieve the script cache entry associated with script_cache_pointer_stack.top(). If this contains a do_udt with the same startIndex, set the elseIndex and endIndex of this do_udt to the values in the cached entry. Else, set them to -1. Do the same for the endIndex.<br />
* Pop the stack. If this value is not 0, increment the PC. If 0, then set the PC to the elseIndex. (If the elseIndex is -1, scan & dispose bytecodes until the else_start command is found, and then update the script_cache.) Increment the PC.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters an "if_end"<br />
| valign="top" | '''Response'''<br />
* Pop the do/if stack stack.top(). If that entry's endIndex is -1, set endIndex = PC and update the script_cache.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters an "else_start" (This will only happen if the else branch is not taken)<br />
| valign="top" | '''Response'''<br />
* Pop the do/if stack stack.top(). If that entry's elseIndex is -1, set elseIndex = PC and update the script_cache.<br />
* Set the PC = endIndex. (If endIndex is -1, scan & dispose bytecodes until the if_end command is found, and then update the script cache.) Increment the PC<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#C8FFC8" colspan=2 |'''''Loops & Flow Control'''''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "do_start"<br />
| valign="top" | '''Response'''<br />
* Push a new do_udt with startIndex = PC to the do/if stack stack.top()<br />
* If the script_cache entry for this script contains a do_udt with the same startIndex, set the endIndex of this do_udt to that value. Else, set it to -1. Set elseIndex = -2.<br />
* Increment the PC<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "do_end"<br />
| valign="top" | '''Response'''<br />
* Pop the do/if stack stack.top(). If that entry's endIndex is -1, set endIndex = PC and update the script_cache.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "break" or "break_x"<br />
| valign="top" | '''Response'''<br />
* Set '''x''' = 1 if this is a "break" command, or '''x''' = stack.pop() if this is "break_x"<br />
* while x > 0, do the following:<br />
* Pop the do/if stack stack.top() until you encounter a do_udt with elseIndex == -2. Pop that as well.<br />
* Set PC = endIndex of this do_udt. (If endIndex is -1, scan & discard bytecodes until a do_end is found. Then update the script_cache.) Increment the PC.<br />
* Decrement '''x'''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "continue" or "continue_x"<br />
| valign="top" | '''Response'''<br />
* Set '''x''' = 1 if this is a "continue" command, or '''x''' = stack.pop() if this is "continue_x"<br />
* Pop the do/if stack stack.top() until you have removed '''x''' do_udts with elseIndex == -2. Push the last entry back to the do/if stack stack.top().<br />
* Set PC = do/if_stack_stack.top().top().startIndex + 1<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#FFFFFF" colspan=2 |'''''Subroutines & Strings'''''<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM encounters a "string define" with string '''test string'''.<br />
| valign="top" | '''Response'''<br />
* Push '''test string''' to the end of the string table<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM encounters a "begin define" with string '''my_function'''.<br />
| valign="top" | '''Response'''<br />
* Look for '''my_function''' in the script name multiplexer. If it's not there, add it and set definitions = new Array[int]<br />
* Create a new entry in the script_cache:<br />
** New Name = "my_function"<br />
** ID = -1<br />
** Pinned = true<br />
** In Use = 0<br />
** Do Table = new Array[do_UDT]<br />
* Scan all bytecodes up to and including an end_define into the "Bytecode" field for this new script cache entry.<br />
* Push into script_name_multiplexer{"my_function"} a reference to the script cache entry you just created.<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM encounters an "un-define named subroutine" with string '''my_function'''.<br />
| valign="top" | '''Response'''<br />
* Pop the last definition from the script_name_multiplexer{"my_function"} into variable '''x'''.<br />
* If script_name_multiplexer{"my_function"} has no more definitions, remove it.<br />
* Set script_cache[x] to be a null row; remove it from the array if you can do so without introducing inconsistencies. At the very least, de-allocate all its data.<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM encounters an "end_define", or a "return" command in a user-defined script.<br />
| valign="top" | '''Response'''<br />
* If there is a return value in script_udt.local_variable_array[0], push it to the parent script's stack. If not, push the last calculated response ('''$_''')<br />
* Set this script's parent's entry in the running scripts table to Wait = false<br />
* Remove this script from the running scripts table. <br />
* Decrement the in_use field for the script_cache entry for this script_udt<br />
* Delete this script_udt<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM encounters an "end_define" in a subroutine.<br />
| valign="top" | '''Response'''<br />
* Pop the script cache pointer stack, set the the corresponding script cache's entry's In Use to In Use -1<br />
* Pop the Program Counter Stack<br />
* Pop the Do/If Stack<br />
* Pop the Local Variable Hash Stack<br />
|}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=User:Sorlok_reaves&diff=22313
User:Sorlok reaves
2009-02-03T09:57:24Z
<p>Sorlok reaves: /* HVM update */</p>
<hr />
<div>Just another OHR developer, though my FreeBASIC's a bit rusty.<br />
<br />
They say "those who can't do show off what others ''can'' do." As such, I am working on a version of the OHR that runs on phones, so you can take your favorites on the go. Really, I think the medium is quite suitable.<br />
<br />
Note to James: The "ESC" key in the Netbeans emulator sends an ''unconditional'' kill code to any running Midlets. It always exits the emulator. That said, I managed to trap any conditional kill codes, so it should work on your phone (except that BREW won't let it work at all.) <br />
<br />
[[Image:ohrrpgce_fmf_screenshot.jpg]]<br />
<br />
<br />
Here's a screen-shot of the desktop HVM that I'm working on, as a test of the HVM's core functionality.<br />
It can do the Fibonacci sequence now!<br />
<br />
[[Image:Fibonacci_hvm.png]]</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=File:Fibonacci_hvm.png&diff=22312
File:Fibonacci hvm.png
2009-02-03T09:56:56Z
<p>Sorlok reaves: /* The HVM can handle Fibonacci numbers now! */</p>
<hr />
<div>/* The HVM can handle Fibonacci numbers now! */</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Henceforth_VM&diff=22311
Henceforth VM
2009-02-03T09:52:40Z
<p>Sorlok reaves: /* was returning 1 greater, by mistake. Fixed, and now handles zero */</p>
<hr />
<div>{{FMF}}<br />
<br />
The Henceforth Virtual Machine (HVM, Henceforth VM) is used to dynamically interpret Henceforth (HF, Hamsterforth) scripts. The OHRRPGCE FMF does not use Hamsterspeak's traditional HSX/HSZ files; rather, it opts for a stack-based alternative. Henceforth files are stored with the .HF extension.<br />
<br />
<br />
<div style="border: 2px red solid; width: 600px; padding: 10px"><span style="color:#AA0000; font-weight:bold; font-size: 20pt">Warning: </span> This document is complete, except for the "final considerations" section, which will be trivial to add later.<br>We are currently in the process of reviewing the entire design specification, writing a toy interpreter, and polishing up (and segmenting) this document. <br>We'll gladly take your suggestions on the "Discussion" page, but please do not modify, distribute, or trust this document. It will soon be as open to the community as everything else on this Wiki, but it'll take a few weeks to do the final review.<br><br>Thanks~<br>/Seth<br />
</div><br />
<br />
<br />
=Acknowledgements=<br />
<br />
A big thanks to both James and Ralph for their well-founded suggestions regarding the HVM, and to Mike for clarifying countless issues. If you start reading into the minute details of the HVM, you'll find that a whole lot of it is lean, clean, and sensible. This is almost entirely due to the suggestions and experience of the OHR's patriarchs, which I truly appreciate. <br />
<br />
<br />
=Introduction to Henceforth=<br />
<br />
Henceforth is a simple, stack-based programming language which the OHRRPGCE FMF uses as a crossover language from traditional Hamsterspeak. Like all of the lumps exclusive to this project, .[[HF]] lumps containing Henceforth code are cross-compiled automatically for you when you convert your game to XRPG Format. However, all developers should consider learning basic Henceforth syntax to aid them in writing run-time debug scripts and compile-time optimized versions of their most time-critical Hamsterspeak scripts. <br />
<br />
Henceforth is a stack-based language, designed to obviate register allocation problems and minimize script size. You should read James's [[How_does_plotscripting_work%3F#The_Stack | excellent analogy of a stack]], if you aren't familiar with the concept. Hamsterspeak uses the stack for ordering scripts; Henceforth, however, uses the stack for storing data.<br />
<br />
<br />
==A Henceforth Primer==<br />
<br />
Although Henceforth is not a compiled language, you can pretend that "source" Henceforth is compiled into "binary" HFZ lumps, which the game engine runs, and only [[User:Sorlok_reaves | one person]] will cringe and correct you. So let's have a look at some Henceforth source code, shall we? Any text after a [http://dictionary.reference.com/browse/hash%20mark hash mark] until the next newline is considered a comment, and [http://dictionary.reference.com/browse/whitespace whitespace] is used to separate commands. Consider the following script.<br />
#One way to separate commands is by hitting "Enter" after each command.<br />
4 #Push 4<br />
5 #Push 5<br />
add #Pop the top two elements and add them. Push the result.<br />
@result #Pop the top element and store it in the variable "result".<br />
<br />
#Another way to separate commands is using spaces.<br />
4 5 add @result<br />
Most programmers will use a mix of spaces and newlines to keep their scripts clear and sensible. For example, "4 5 add" should probably always be on the same line. Regardless of what format you use, manipulation of the stack is the same. The following diagram shows how the stack (and the variables list) changes as the HVM reads each command, from left to right.<br />
<br />
[[Image:Fmf_stack_diagram.png]]<br />
<br />
<br />
==The "Pure" Virtual Machine==<br />
<br />
From a theoretical point of view, Henceforth does not require a location to store variables. Rather, we could simply add '''block labels''' and a delayed execution ('''prepending''') command:<br />
* Surrounding some source with '''\name{}''' will label that block with "name"; if '''name''' later appears as a command, this block will be inserted in-place. Later writing '''/name''' will un-define "name". If multiple blocks are labeled similarly, the most-recent label is considered valid. <br />
* Pre-pending "&" to a command will push the result of that command ''into the source code itself'', at the time that command is first encountered. <br />
<br />
'''Block labels''' allow functions to be written very naturally:<br />
#The "modulo" function expects two arguments to be<br />
# on the stack; it transforms (X Y) to (X%Y)<br />
\modulo {<br />
swap dup rot dup rot swap #Now we have (X Y X Y)<br />
div mult sub #The heart of our algorithm<br />
}<br />
There's no need to "return" anything; the labeled block operates in a way that leaves one item on the stack. Here's some code to use this block as a function:<br />
#We want to compute 7%3<br />
7 3 modulo #After this, "1" is left on the stack<br />
<br />
This is, in fact, the exact syntax for writing a function in Henceforth. Now, let's cover '''prepending''':<br />
#Avoiding variables with named blocks.<br />
4 5 add <br />
\result{ #Label the start of the "result" block<br />
&pop #This is tricky: the first time we encounter this<br />
# (i.e., when "\result" is defined), it executes "pop",<br />
# resulting in "9", which is pushed into the source.<br />
} #Closes the "\result" label.<br />
<br />
result #Calling "result" will now push "9".<br />
<br />
Unlike named blocks, prepending is not actually implemented in Henceforth; you can ignore that last bit of code if you don't understand it. Which brings us to our main point:<br />
<br />
<big><big style="color:green;"><br />
'''Henceforth was designed around a "pure" VM'''</big><big>, but it was then modified to </big><big style="color:green;">'''run efficiently'''</big><big> on a phone, and to </big><big style="color:green;">'''allow an easy mapping'''</big><big> from existing Hamsterspeak scripts. <br />
</big></big><br />
<br />
It is crucial that you keep the ideal virtual machine in mind, or else you will start asking about "Henceforth's registers" (which don't exist) or you'll start using variables way too much. You also need to be cognizant of the dirty details required to kludge Hamsterspeak onto a phone; otherwise, things like script-level subroutines and our extravagant caching mechanism will seem redundant and senseless.<br />
<br />
(Note to readers: At this point, if you're only interested in the Henceforth language, you can skip down to the section on [[Henceforth_VM#Henceforth Syntax Dictionary | Henceforth Syntax]].)<br />
<br />
<br />
==Where the HVM Fits Into the Game Loop==<br />
<br />
Let's pretend that on a certain map in your game, a certain NPC is set up to "run script 4" when the user walks up to it and presses "Accept". Upon pressing "Accept", the game engine checks if this NPC has a script ID assigned to its "accept" behavior. The classic OHR performs this same task. If one exists, it is initialized and cached. At this point, the game engine continues with its tasks for that cycle (e.g., message boxes, vehicles). This (including user input) constitutes the "Update" phase of the game loop. Following this is the "Paint" phase, which shows one frame's worth of updates to the user. After painting, the tick count is incremented, and the loop repeats. The first thing that happens then is the "scripts" phase, during which scripts are actually executed (see below). The loop repeats indefinitely.<br />
<br />
[[Image:Hf_flowchart.png]]<br />
<br />
<br />
<br />
===Some Technical Considerations===<br />
<br />
When a script is triggered by any means, it suspends the currently-executing script; see "The Stack" in "[[How does plotscripting work?]]". This makes sense; if your script calls another script, one expects the new script to take over. It also means that if you step on several map tiles which each have (long) scripts, the last maptile's script will finish first. (Threading will probably change this.)<br />
<br />
But wait, there's more! Several small but important details of script initialization include:<br />
# If you trigger the same script twice (e.g., by stepping on two copies of an NPC one after the other) the script loader will detect that you are trying to load a script with the '''same id''' as the currently executing script. If the "Permit double-triggering of scripts" general bitset is on (or if the script is explicitly called by ''another script'') then this is allowed, and a second instance of the script is initialized. <br />
# When a script instance is '''first''' loaded, its "delay" counter is set to 2 ticks. This is ''not'' the same thing as instructing the script to "wait", because it remains the active script while delaying. If another script pre-empts this one, the delay counter does not decrement until the new script finishes. (When the new script finishes, the old one immediately resumes what it was doing, be it running or decrementing the "delay" counter.) <br />
# To prepare for parallel scripts, the active script is pushed to the top of the "waiting" stack, and is manipulated by a reference ID. This way, all scripts are kept in one place, and a future, parallel version of GAME_FMF can just scan through this list once each cycle, updating all scripts until they enter a waiting state.<br />
<br />
<br />
<br />
<br />
=Virtual Hardware Specification=<br />
<br />
The [http://en.wikipedia.org/wiki/Virtual_machine Wikipedia page] for Virtual Machines focuses on their use in emulating existing hardware. In fact, there is no hardware version of the HVM, just like there is no hardware equivalent of the Java Virtual Machine. Rather, Henceforth defines an unambiguous virtual hardware specification which can be ported to any platform with guaranteed interoperability. Currently, the only platform with a working HVM is the OHRRPGCEFMF, due to the bias of the HVM's design towards mobile devices.<br />
<br />
Anything in the world can be described by its nouns and verbs --the things it '''has''' and what they '''do'''. So let's inspect the '''pieces''' of the HVM and how they '''interact'''.<br />
<br />
<br />
==VM Data Structures==<br />
<br />
[[Image:Fmf_hvm_data_structures.png]]<br />
<br />
Green and red lines in this diagram indicate references from one item to another, although they are not drawn for every single reference (because that would be craaaaaaazy). <br />
<br />
The Script UDT block is particularly garish, because it is accommodating two different script types: user-defined scripts and named subroutines. Each component is colored differently depending on its use.<br />
<br />
<br />
=== What these do, top-down ===<br />
<br />
One way of learning how these data structures work is by discussing what each piece does on its own, and then assuming their combination. This is like studying one of those [http://www.applesource.com/peeler.html apple machines]; we say that this piece pierces the apple and holds it, that piece slices the skin off, and that blade cores the apple.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_top-down.png|link=FMF:The_HVM,_From_the_Top_Down]] || valign="top" | '''[[FMF:The_HVM,_From_the_Top_Down | Top-Down Description of the HVM]]'''<br>Read on for a detailed summary of the HVM's parts.<br />
|}<br />
<br />
<br />
<br />
===What These Do: Bottom-Up Description===<br />
<br />
If you've ever seen an [http://www.applesource.com/peeler.html apple machine] like the one just mentioned, you're probably aware that describing each element does absolutely nothing to help you understand how the thing works --you have to actually use it on an apple (preferably very slowly) to see how the magic is done. This is the essence of a bottom-up description: describe how certain parts '''interact''' at critical moments in the operation of the whole.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_bottom-up.png|link=FMF:The_HVM,_From_the_Bottom_Up]] || valign="top" | '''[[FMF:The_HVM,_From_the_Bottom_Up | Bottom-Up Description of the HVM]]'''<br>Read on for a detailed summary of how the HVM's parts interact.<br />
|}<br />
<br />
<br />
<br />
<br />
=HF Source Code & Formats=<br />
<br />
Strictly speaking, Henceforth has three different formats for its source code, and there is no such thing as "compiled Henceforth". These three formats are '''Format T''' (text-based), '''Format B''' (bytecode), and '''Format HF''' (compressed). Practically, most user code is generated in '''Format T''', which is then converted to '''Format B''' and compressed (to '''Format HF''') for storage. The HVM reads all three formats, but in doing so it converts T & HF to B. <br />
<br />
That said, we shall begin our study of Henceforth syntax by exhaustively reviewing all Henceforth commands in "source code" format ('''Format T'''), and then describe the mappings to other formats. A few commands cannot be explicitly represented in text format, as the following table details. We shall provide a recommended way of typing these when possible.<br />
<br />
{| cellspacing=0<br />
| &nbsp; || style="border: 1px solid black;" | '''Type''' || style="border: 1px solid black;" | '''Use'''<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format T || valign="top" style="border: 1px solid black;" | text || valign="top" style="border: 1px solid black;" | All human interaction (coding, discussing ideas, etc.) occurs in this format. Thins like script-local subroutines and function parameters cannot be expressed explicitly in this format.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format B || valign="top" style="border: 1px solid black;" | bytecode (binary) || valign="top" style="border: 1px solid black;" | The HVM executes Format B code. The HSP2HF cross-compiler outputs this format. Contains Henceforth Extensions for Scripting.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format HF || valign="top" style="border: 1px solid black;" | compressed || valign="top" style="border: 1px solid black;" | The XRPG specification requires all Format B code to be compressed into format HF lumps, which are then stored locally on the user's phone.<br />
|}<br />
<br />
Henceforth source is a rather slim language. Numbers and strings are supported, but concatenations like "Hello"+" world" and decimals like 2.4 are not. A syntax exists for "do" and "if" statements, and for declaring functions and variables. Several smaller hacks exist for efficiency's sake, as described earlier. And that's it. Anything else is either a "primitive" like '''add''', or a "block label", subject to the polymorphic lookup that characterizes Forth-like languages. Henceforth is not based explicitly on Classic Forth, but it hopes to learn from the mistakes of this peculiar family of languages. One of the main disadvantages to ANSI Forth is its pandering to ANSI C fixations, overcomplicating a language which, at its heart, always championed simplicity and elegance.<br />
<br />
<br />
==Format B Bytecode Specification (HFB)==<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_format-b.png|link=FMF:HFB]] || valign="top" | '''[[FMF:HFB | Format B Specification]]'''<br>Format B code is detailed into exhaustion on a separate page.<br />
|}<br />
<br />
<br />
<br />
<br />
==Henceforth Syntax Dictionary==<br />
<br />
This section contains all of the commands that you could enter into a run-time interpreter (if such a one existed). There is no reference to HSpeak scripts, GAME API calls, or anything else; this section simply explains Henceforth as a language.<br />
<br />
Think of it this way: A Henceforth "script" (called a '''subroutine''') is a slimmed-down version of a Henceforth HSpeak "script" (called a '''script'''). A script maintains a separate stack, for one thing, and script-local subroutines, for another. So, by way of example, calling "wait" within a subroutine would cause the HVM to crash. A script cannot be specified in textual format -that's only a recommended part of the specification, not required. A global script called "root" is used to load subroutines which would otherwise have no parent script.<br />
<br />
<br />
===Numbers===<br />
<br />
Numbers are represented by signed integers such as 2, 18, -35, or 43. When the HVM encounters a number, it pushes it to the stack. That's all for numbers, so let's cover stack effect diagrams briefly; they're necessary to understand more complicated features. Consider:<br />
{| border="1" cellpadding="3" rules="all"<br />
|-<br />
| <tt style="font-size:10pt">1 2 3</tt>|| <tt style="font-size:10pt">7</tt> || <tt style="font-size:10pt">1 2 3 7</tt><br />
|}<br />
<br />
(Note that stack-effect diagrams are oriented with the top of the stack on the right.)<br />
<br />
This diagram implies that the stack starts with the elements 1, 2, and 3 already in it, is then given the command "7", and the resulting stack is now 1, 2, 3, then 7. These is an example of the ''prototype'' style of learning; to teach you how to use a command, I give you a real-world example of how it works. <br />
<br />
If I want to write a stack effect diagram without the fancy table, I might use parentheses and the "--" symbol to mean "the operator we're talking about". For example: "if the interpreter encounters -5, the resulting stack-effect diagram would be <tt style="font-size:10pt">(1 2 3 -- 1 2 3 -5)</tt>". <br />
<br />
For convenience, hexadecimal numbers are allowed, with a '''0x''' prefix. Internally, however, they are simply stored as decimals.<br />
<br />
<br />
===Primitives===<br />
<br />
When the HVM interprets a literal string like '''exp''', '''mycode''', or '''abs''', it searches for the relevant subroutine. The primitives are exceptions to this; they are always executed immediately. A primitive consists of lowercase letters and underscores, and must contain at least one letter. We call this combination its '''symbol name'''.<br />
<br />
Here is a list of primitives, and their stack-effect diagrams:<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Primitive''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="5"| Stack Manipulation || <tt style="font-size:10pt">dup</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 3 )</tt> || "Duplicate" the top of the stack.<br />
|-<br />
| <tt style="font-size:10pt">swap</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 3 2 )</tt> || "Swap" the top two elements of the stack.<br />
|-<br />
| <tt style="font-size:10pt">drop</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 )</tt> || "Drop" the top element off the stack.<br />
|-<br />
| <tt style="font-size:10pt">over</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 2 )</tt> || Pull the penultimate element "over" the top element.<br />
|-<br />
| <tt style="font-size:10pt">rot</tt> || <tt style="font-size:10pt">( 1 2 3 4 -- 1 4 3 2 )</tt> || "Rotate" the top three elements of the stack.<br />
|-<br />
| rowspan="5"| Arithmetic || <tt style="font-size:10pt">add</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 5 )</tt> || Addition<br />
|-<br />
| <tt style="font-size:10pt">sub</tt> || <tt style="font-size:10pt">( 7 3 2 -- 7 1 )</tt> || Subtraction<br />
|-<br />
| <tt style="font-size:10pt">mult</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 6 )</tt> || Multiplication<br />
|-<br />
| <tt style="font-size:10pt">div</tt> || <tt style="font-size:10pt">( 1 7 3 -- 1 2 )</tt> || Division<br />
|-<br />
| <tt style="font-size:10pt">random</tt> || <tt style="font-size:10pt">( 1 4 8 -- 1 6 )</tt> || Push a random number to the stack.<br />
|-<br />
| rowspan="4"| Bitwise Operators || <tt style="font-size:10pt">b_xor</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 17 )</tt> || Exclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_or</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 53 )</tt> || Inclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_and</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 36 )</tt> || Bitwise "And"<br />
|-<br />
| <tt style="font-size:10pt">b_not</tt> || <tt style="font-size:10pt">( 1 52 -- 1 2147483595'''''**''''')</tt> || Bitwise "Not"<br />
|-<br />
| rowspan="6"| Logical Operators || <tt style="font-size:10pt">eq</tt> || <tt style="font-size:10pt">( 1 2 2 -- 1 1 )</tt> || Logical "Equals"<br />
|-<br />
| <tt style="font-size:10pt">lt</tt> || <tt style="font-size:10pt">( 1 5 2 -- 1 0 )</tt> || Logical "Less Than"<br />
|-<br />
| <tt style="font-size:10pt">not</tt> || <tt style="font-size:10pt">( 1 5 1 -- 1 5 0 )</tt> || Logical "Not"<br />
|-<br />
| <tt style="font-size:10pt">and</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 1 )</tt> || Logical "And"<br />
|-<br />
| <tt style="font-size:10pt">or</tt> || <tt style="font-size:10pt">( 1 5 0 1 -- 1 5 1 )</tt> || Inclusive Logical "Or"<br />
|-<br />
| <tt style="font-size:10pt">xor</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 0 )</tt> || Exclusive Logical "Or"<br />
|}<br />
'''''**'''''Please note: For now, the HVM doesn't use two's compliment to store integers. Hence, ~52 is masked with INT_MAX, which is 0x7FFFFFFF in 32-bit notation. We will probably change this for version 2.0, since people who use b_not (e.g., hackers) will be expecting the opposite behavior.<br />
<br />
<br />
There is no boolean type; the number zero is considered "false", and any other number is considered "true". For clarity, the number "1" is the preferred way to represent "true".<br />
<br />
The primitives "and" and "or" do ''not'' short-circuit. This is because a short-circuiting operator generates an ambiguous stack-effect diagram, and achieves no gain in performance or security (since it operates on values which have already been computed.) The [[HSP2HF | Cross-Compiler]] has an example of how to perform short-circuiting calculations in a manual way; see the section on Mathematical Functions.<br />
<br />
A number of additional keywords such as '''break''' and '''continue''' will be discussed in the section on If statements and Loops.<br />
<br />
===Blocks (Subroutines)===<br />
<br />
Labeled blocks, also called subroutines, are the core of Henceforth's modularization. They are strikingly similar to primitives, except that they can be defined, undefined, and overloaded. Labels, like primitives, are identified by a '''symbol name'''.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This creates a function called "mod", which performs a series of stack manipulations and basic arithmetic.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;7 3 mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Traditionally, we push the "parameters" to the subroutine in increasing order, and expect it to remove these arguments (and replace them with a return value, if any) during the course of its operation.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Overriding a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;drop drop 0<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;After executing this snippet, "mod" will have two definitions. Calling "mod" will result in the latter being evoked, which simply clears the parameters and returns zero.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Un-defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">/NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;/mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;If we execute the code from "Overriding a Subroutine", and then execute this code, "mod" will be left with a single definition: the first one.<br />
|}<br />
<br />
<br />
Any labeled block remains in effect even if the script which defined it finishes execution; make sure to un-define any subroutines you do not wish to linger. The HVM capitalizes on this behavior to limit the number of built-in primitives. The modulus operator described earlier is an example, as is "less than or equal to", which can be defined compositely. Here is a list of all the subroutines the HVM loads by default, with their stack-effect diagrams.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Subroutine''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="2"| Arithmetic || <tt style="font-size:10pt">exp</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 8 )</tt> || Compute x raised to the y ("exponent")<br />
|-<br />
| <tt style="font-size:10pt">mod</tt> || <tt style="font-size:10pt">( 1 10 4 -- 1 2 )</tt> || Calculate the "modulus"<br />
|-<br />
| rowspan="4"| Logical Operators || <tt style="font-size:10pt">neq</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 1 )</tt> || Logical "Not Equal To"<br />
|-<br />
| <tt style="font-size:10pt">lte</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 1 )</tt> || Logical "Less Than or Equal To"<br />
|-<br />
| <tt style="font-size:10pt">gt</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 0 )</tt> || Logical "Greater Than"<br />
|-<br />
| <tt style="font-size:10pt">gte</tt> || <tt style="font-size:10pt">( 1 63 45 -- 1 1 )</tt> || Logical "Greater Than or Equal To"<br />
|-<br />
| rowspan="2"| Convenience Methods || <tt style="font-size:10pt">true</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 45 52 1 )</tt> || Locus-of-control for "true"<br />
|-<br />
| <tt style="font-size:10pt">false</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 45 45 0 )</tt> || Locus-of-control for "false"<br />
|}<br />
<br />
<br />
===Variables===<br />
<br />
Shuffling the stack beyond the fourth element can get messy. As such, subroutines can allocate ''local variables'' to the scope of the parent '''script''' they are running in. Note that variables follow a similar naming convention to subroutines. They are accessed using the @VAR and VAR@ commands. You can remember them easily by putting an imaginary "stack" next to them: <br />
* '''(stack)@VAR''' takes a value from the stack and throws it '''at''' VAR. This is the same as setting VAR = pop().<br />
* '''VAR@(stack)''' takes the value in VAR and throws it '''at''' the stack. This is the same as pushing VAR.<br />
<br />
Here is some sample source for the '''lte''' function, written using variables. Note that, in reality, '''lte''' only uses the stack. This is much more efficient for the HVM, which only initializes the local variable store if it detects your script trying to access it.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using Variables'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\lte{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Pop the stack twice to get our local variables.<br>&nbsp;&nbsp;&nbsp;&nbsp;#We assume that we are testing @lhs <= @rhs<br>&nbsp;&nbsp;&nbsp;&nbsp;@rhs&nbsp;&nbsp;@lhs<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the first test (<) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res1<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;lt&nbsp;&nbsp;@res1<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the second test (==) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res2<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;eq&nbsp;&nbsp;@res2<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our two tests and do an or test. Leave it on<br>&nbsp;&nbsp;&nbsp;&nbsp;# the stack to be returned<br>&nbsp;&nbsp;&nbsp;&nbsp;res1@&nbsp;&nbsp;res2@&nbsp;&nbsp;or<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;As an exercise, you should try to convert this to a stack-based alternative. It should end up more concise, and also faster. (Profile it, to be sure).<br />
|}<br />
<br />
<br />
===Do/If & Loops===<br />
<br />
Most Forth-like languages have an "if" statement, and Henceforth is no exception. However, rather than encapsulating loops into labeled blocks (like Classic Forth), it adds another keyword: "do". This serves two purposes: first, it allows for better inlining than subroutines, and second, it provides a locus of control for jump commands. The second rationale is sometimes written as "otherwise, we'd have to use '''goto'''".<br />
<br />
Inside of a do loop, the commands '''break''' and '''continue''' also take effect; you should read [[HSZ#Id_11:_break | the HSZ documentation]] for an example of how these work conceptually. The mechanism for '''break''' and '''continue''' is different in the HVM, but the effect is the same.<br />
<br />
The standard Hamsterspeak engine also allows breaking out of multiple loops at once. In order to keep the syntax clean in Henceforth, we introduce two bytecodes for that purpose: '''break_x''' and '''continue_x'''. These take the element from the top of the stack, and break out of that many loops. The '''break''' command is effectively equivalent to "1 break_x". <br />
<br />
Thus, we add five more keywords to the list of Henceforth primitives: '''do''', '''if''', '''else''', '''break''', and '''continue'''. Brackets are used to denote code blocks where relevant. We will cover the syntax in simple examples. The first is a user-defined subroutine, "divide_safe", which avoids dividing by zero, and the second is an example of the Fibonacci sequence using "do" to achieve the effect of a "while" loop.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Save Divide-by-Zero Handling'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\divide_safe{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Do a zero-check on the divisor<br>&nbsp;&nbsp;&nbsp;&nbsp;dup&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;#Now, branch on that<br>&nbsp;&nbsp;&nbsp;&nbsp;if{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drop&nbsp;&nbsp;drop&nbsp;&nbsp;0<br>&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;div<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Note that returning zero is just one way of handling the erroneous case. Also, take note of the two "drop" commands; these are required!<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Fibonacci Numbers'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\fibonacci{<br>&nbsp;&nbsp;&nbsp;&nbsp;@count&nbsp;&nbsp;&nbsp;&nbsp; #Must be >=0<br>&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;@fprev&nbsp;&nbsp;#fib(n-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;@fnow&nbsp;&nbsp; #fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Test-and-decrement<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;-1&nbsp;&nbsp;add&nbsp;&nbsp;@count<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Iterate & Accumulate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprev@&nbsp;&nbsp;fnow@&nbsp;&nbsp; add<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fnow@&nbsp;&nbsp; @fprev<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fnow<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Re-do the loop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fprev@<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This subroutine fails given zero as input. Modify it by adding a simple if statement before the main loop. <br />
|}<br />
<br />
This second example is far more interesting, since it answers the question "How do I Loop in Henceforth?"<br />
<br />
'''''A Note About Recursion''''': Although Henceforth fully supports recursion, the HVM is not designed for heavily-recursive functions. When possible, please use an iterative solution.<br />
<br />
==Henceforth Extensions for Scripting==<br />
<br />
The Henceforth specification recommends (read: ''does not require'') that the following pseudo-syntax be adopted to describe the commands which appear only in the '''Format B''' code for HSpeak script equivalents. A vanilla HVM should recognize these tokens and either ignore them, simulate them, or generate an informative error message when it encounters them.<br />
<br />
Backwards compatibility need not be preserved when converting between the '''Format B''' and '''Format T''' equivalents for script extensions.<br />
<br />
<br />
<br />
===HSpeak API Calls & User Scripts===<br />
<br />
The HVM has knowledge of most "built-in" functions that Hamsterspeak provides. These are called like any other subroutine, with one exception: most format converters create specialized subroutine calls out of them. In other words, if I write a call to '''wait_npc''' in my ('''Format T''') code, the converter will create ('''Format B''') source that points ''explicitly'' to the definition of '''wait_npc''' in the HVM; it is '''''not''''' possible to overload '''wait_npc'''. This breaks our promise of fully-interchangeable source formats, but it is hardly noticeable to most users, and keeps our global function lookup table from becoming too cluttered.<br />
<br />
See [[FMF:Plotdict]] for a listing of all API calls and their syntax in Henceforth.<br />
<br />
User-defined scripts are treated exactly like API calls; they are assigned IDs and called explicitly. <br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Calling an HSpeak API Method'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;12<br>&nbsp;&nbsp;show_txt</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Shows text box with ID 12. The api call '''show_txt''' is the shorthand for "show text box", [[FMF:Plotdict | as listed here]]. <br />
|}<br />
<br />
<br />
===Strings===<br />
<br />
Strings in Classic Forth are handled by simply pushing a series of integers (for each letter) to the stack. Eventually, Henceforth may go this route, but for now the API calls to strings are so simple that we chose a much simpler implementation:<br />
* Strings are a series of letters between double-quotes, like "Hello World", "Going?", or "Unicode: \u1000". As hinted by the last example, the HVM supports Unicode.<br />
* When a string is encountered (usually at the top of a script) it is pushed to the '''String Table''' for that script. Strings are pushed in order, cannot be removed or easily manipulated, and have IDs in increasing order.<br />
<br />
This is an ''ad hoc'' implementation of strings, until we get a clearer picture where the standard OHR's development is going with these ideas.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using a String'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;"Hello world!"<br>&nbsp;&nbsp;0 show_str</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Defining "Hello world!" will place this string at index zero; pushing 0 and then calling the API function '''show_str''' will reference that string explicitly.<br />
|}<br />
<br />
<br />
===Script-Local Subroutines===<br />
<br />
Here's where Henceforth starts to really get picky. When a Hamsterspeak script is converted to Henceforth, it is basically converted into a series of labeled blocks that call each other. The keen observer will note that representing these blocks as subroutines is inefficient, since they can be consistently enumerated. <br />
<br />
With that in mind, subroutines are allowed to define "script-local" subroutines which operate along a single thread of control within a named block. These are defined with a given ID, and called with an array-like syntax. They cannot be un-defined or overloaded, although theoretically they could be replaced (by re-defining them).<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\[ID]{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\[5]{<br>&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;plus<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Our "plus two" example from before, assigned to index 5.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">[ID]()</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;16&nbsp;&nbsp;[5]()</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This pushes 16, and calls local block 5. The result is a stack containing the number 18, if we define [5] as listed above. ''The parentheses, although not technically necessary, do a great deal to visually distinguish script-local subroutines from function parameters.''<br />
|}<br />
<br />
<br />
===Local Parameters & Return Values===<br />
<br />
'''''MAKE A NOTE: Give an example of how my_script{ exp } calls "exp" ON the stack of my_script. '''''<br />
<br />
An astute reader will have noticed that scripts currently have no way of passing values to other scripts. Calling "5 6 my_func" won't work, because '''my_func''' will get its own independent stack. This problem is circumvented through the use of local parameters, which also provide a slight speedup for small, frequently called subroutines. The local parameters are accessed by ID, with -1 a special ID that refers to the script's return value. <br />
* Executing @[5] will "pop the (parent) stack to local parameter 5"<br />
* Executing [5]@ will "push local parameter 5 onto the (current) stack<br />
For the return value, it's backwards:<br />
* Executing @[-1] will "pop the (current) stack to the return value"<br />
* Executing [-1]@ will "push the return value onto the (parent) stack", '''''unless''''' the return value is set to ''invalid'' (INT_MIN, for now).<br />
The only point of interest is that last bit. The problem is, the HSpeak command "return" sets the return value, and "exitscript" will exit and return that value. Some scripts simply don't set a return value, so the HVM pushes the '''most recently computed integer''' if it detects an ''invalid'' return value.<br />
<br />
===Comments===<br />
<br />
Henceforth supports comments, although it's important to remember that Formats B and HF will drop your comments; they can't be encoded. A comment begins with a hash mark (#) and ends with a line feed. <br />
<br />
<br />
===Global Variables===<br />
<br />
All that's left are global variables, which are manipulated with syntax like '''@[65.G]''' and '''[65.G]@'''. We realize this is a bit awkward, and we're open to a cleaner syntax (once we actually get a release going).<br />
<br />
<br />
<br />
= The Cross-Compiler =<br />
<br />
The OHRRPGCE FMF provides a tool to help convert existing Hamster Speak scripts to Henceforth lumps; read about it here:<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_hsp2hf.png|link=HSP2HF]] || valign="top" | '''[[HSP2HF | The HSP2HF Cross-Compiler]]'''<br>Read on for details about this tool.<br />
|}<br />
<br />
<br />
=Final Considerations=<br />
<br />
Henceforth is a big step away from Hamsterspeak, and it is best to approach it with an open mind. Try solving [http://projecteuler.net/index.php?section=problems common problems] with snippets of Henceforth; even if you simply write the solution on paper it will help you get used to thinking in a stack-based fashion. <br />
<br />
The developers of Henceforth are always interested in advancing the language; feel free to post a message [[User_talk:Sorlok_reaves | on Seth's page]] with your ideas --except, try to avoid buzzwords. Henceforth will never have objects, threading (which is ''different'' from multi-tasking), or static typing; it's just not that kinda language.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Henceforth_VM&diff=22310
Henceforth VM
2009-02-03T09:44:03Z
<p>Sorlok reaves: /* "drop", not "pop" */</p>
<hr />
<div>{{FMF}}<br />
<br />
The Henceforth Virtual Machine (HVM, Henceforth VM) is used to dynamically interpret Henceforth (HF, Hamsterforth) scripts. The OHRRPGCE FMF does not use Hamsterspeak's traditional HSX/HSZ files; rather, it opts for a stack-based alternative. Henceforth files are stored with the .HF extension.<br />
<br />
<br />
<div style="border: 2px red solid; width: 600px; padding: 10px"><span style="color:#AA0000; font-weight:bold; font-size: 20pt">Warning: </span> This document is complete, except for the "final considerations" section, which will be trivial to add later.<br>We are currently in the process of reviewing the entire design specification, writing a toy interpreter, and polishing up (and segmenting) this document. <br>We'll gladly take your suggestions on the "Discussion" page, but please do not modify, distribute, or trust this document. It will soon be as open to the community as everything else on this Wiki, but it'll take a few weeks to do the final review.<br><br>Thanks~<br>/Seth<br />
</div><br />
<br />
<br />
=Acknowledgements=<br />
<br />
A big thanks to both James and Ralph for their well-founded suggestions regarding the HVM, and to Mike for clarifying countless issues. If you start reading into the minute details of the HVM, you'll find that a whole lot of it is lean, clean, and sensible. This is almost entirely due to the suggestions and experience of the OHR's patriarchs, which I truly appreciate. <br />
<br />
<br />
=Introduction to Henceforth=<br />
<br />
Henceforth is a simple, stack-based programming language which the OHRRPGCE FMF uses as a crossover language from traditional Hamsterspeak. Like all of the lumps exclusive to this project, .[[HF]] lumps containing Henceforth code are cross-compiled automatically for you when you convert your game to XRPG Format. However, all developers should consider learning basic Henceforth syntax to aid them in writing run-time debug scripts and compile-time optimized versions of their most time-critical Hamsterspeak scripts. <br />
<br />
Henceforth is a stack-based language, designed to obviate register allocation problems and minimize script size. You should read James's [[How_does_plotscripting_work%3F#The_Stack | excellent analogy of a stack]], if you aren't familiar with the concept. Hamsterspeak uses the stack for ordering scripts; Henceforth, however, uses the stack for storing data.<br />
<br />
<br />
==A Henceforth Primer==<br />
<br />
Although Henceforth is not a compiled language, you can pretend that "source" Henceforth is compiled into "binary" HFZ lumps, which the game engine runs, and only [[User:Sorlok_reaves | one person]] will cringe and correct you. So let's have a look at some Henceforth source code, shall we? Any text after a [http://dictionary.reference.com/browse/hash%20mark hash mark] until the next newline is considered a comment, and [http://dictionary.reference.com/browse/whitespace whitespace] is used to separate commands. Consider the following script.<br />
#One way to separate commands is by hitting "Enter" after each command.<br />
4 #Push 4<br />
5 #Push 5<br />
add #Pop the top two elements and add them. Push the result.<br />
@result #Pop the top element and store it in the variable "result".<br />
<br />
#Another way to separate commands is using spaces.<br />
4 5 add @result<br />
Most programmers will use a mix of spaces and newlines to keep their scripts clear and sensible. For example, "4 5 add" should probably always be on the same line. Regardless of what format you use, manipulation of the stack is the same. The following diagram shows how the stack (and the variables list) changes as the HVM reads each command, from left to right.<br />
<br />
[[Image:Fmf_stack_diagram.png]]<br />
<br />
<br />
==The "Pure" Virtual Machine==<br />
<br />
From a theoretical point of view, Henceforth does not require a location to store variables. Rather, we could simply add '''block labels''' and a delayed execution ('''prepending''') command:<br />
* Surrounding some source with '''\name{}''' will label that block with "name"; if '''name''' later appears as a command, this block will be inserted in-place. Later writing '''/name''' will un-define "name". If multiple blocks are labeled similarly, the most-recent label is considered valid. <br />
* Pre-pending "&" to a command will push the result of that command ''into the source code itself'', at the time that command is first encountered. <br />
<br />
'''Block labels''' allow functions to be written very naturally:<br />
#The "modulo" function expects two arguments to be<br />
# on the stack; it transforms (X Y) to (X%Y)<br />
\modulo {<br />
swap dup rot dup rot swap #Now we have (X Y X Y)<br />
div mult sub #The heart of our algorithm<br />
}<br />
There's no need to "return" anything; the labeled block operates in a way that leaves one item on the stack. Here's some code to use this block as a function:<br />
#We want to compute 7%3<br />
7 3 modulo #After this, "1" is left on the stack<br />
<br />
This is, in fact, the exact syntax for writing a function in Henceforth. Now, let's cover '''prepending''':<br />
#Avoiding variables with named blocks.<br />
4 5 add <br />
\result{ #Label the start of the "result" block<br />
&pop #This is tricky: the first time we encounter this<br />
# (i.e., when "\result" is defined), it executes "pop",<br />
# resulting in "9", which is pushed into the source.<br />
} #Closes the "\result" label.<br />
<br />
result #Calling "result" will now push "9".<br />
<br />
Unlike named blocks, prepending is not actually implemented in Henceforth; you can ignore that last bit of code if you don't understand it. Which brings us to our main point:<br />
<br />
<big><big style="color:green;"><br />
'''Henceforth was designed around a "pure" VM'''</big><big>, but it was then modified to </big><big style="color:green;">'''run efficiently'''</big><big> on a phone, and to </big><big style="color:green;">'''allow an easy mapping'''</big><big> from existing Hamsterspeak scripts. <br />
</big></big><br />
<br />
It is crucial that you keep the ideal virtual machine in mind, or else you will start asking about "Henceforth's registers" (which don't exist) or you'll start using variables way too much. You also need to be cognizant of the dirty details required to kludge Hamsterspeak onto a phone; otherwise, things like script-level subroutines and our extravagant caching mechanism will seem redundant and senseless.<br />
<br />
(Note to readers: At this point, if you're only interested in the Henceforth language, you can skip down to the section on [[Henceforth_VM#Henceforth Syntax Dictionary | Henceforth Syntax]].)<br />
<br />
<br />
==Where the HVM Fits Into the Game Loop==<br />
<br />
Let's pretend that on a certain map in your game, a certain NPC is set up to "run script 4" when the user walks up to it and presses "Accept". Upon pressing "Accept", the game engine checks if this NPC has a script ID assigned to its "accept" behavior. The classic OHR performs this same task. If one exists, it is initialized and cached. At this point, the game engine continues with its tasks for that cycle (e.g., message boxes, vehicles). This (including user input) constitutes the "Update" phase of the game loop. Following this is the "Paint" phase, which shows one frame's worth of updates to the user. After painting, the tick count is incremented, and the loop repeats. The first thing that happens then is the "scripts" phase, during which scripts are actually executed (see below). The loop repeats indefinitely.<br />
<br />
[[Image:Hf_flowchart.png]]<br />
<br />
<br />
<br />
===Some Technical Considerations===<br />
<br />
When a script is triggered by any means, it suspends the currently-executing script; see "The Stack" in "[[How does plotscripting work?]]". This makes sense; if your script calls another script, one expects the new script to take over. It also means that if you step on several map tiles which each have (long) scripts, the last maptile's script will finish first. (Threading will probably change this.)<br />
<br />
But wait, there's more! Several small but important details of script initialization include:<br />
# If you trigger the same script twice (e.g., by stepping on two copies of an NPC one after the other) the script loader will detect that you are trying to load a script with the '''same id''' as the currently executing script. If the "Permit double-triggering of scripts" general bitset is on (or if the script is explicitly called by ''another script'') then this is allowed, and a second instance of the script is initialized. <br />
# When a script instance is '''first''' loaded, its "delay" counter is set to 2 ticks. This is ''not'' the same thing as instructing the script to "wait", because it remains the active script while delaying. If another script pre-empts this one, the delay counter does not decrement until the new script finishes. (When the new script finishes, the old one immediately resumes what it was doing, be it running or decrementing the "delay" counter.) <br />
# To prepare for parallel scripts, the active script is pushed to the top of the "waiting" stack, and is manipulated by a reference ID. This way, all scripts are kept in one place, and a future, parallel version of GAME_FMF can just scan through this list once each cycle, updating all scripts until they enter a waiting state.<br />
<br />
<br />
<br />
<br />
=Virtual Hardware Specification=<br />
<br />
The [http://en.wikipedia.org/wiki/Virtual_machine Wikipedia page] for Virtual Machines focuses on their use in emulating existing hardware. In fact, there is no hardware version of the HVM, just like there is no hardware equivalent of the Java Virtual Machine. Rather, Henceforth defines an unambiguous virtual hardware specification which can be ported to any platform with guaranteed interoperability. Currently, the only platform with a working HVM is the OHRRPGCEFMF, due to the bias of the HVM's design towards mobile devices.<br />
<br />
Anything in the world can be described by its nouns and verbs --the things it '''has''' and what they '''do'''. So let's inspect the '''pieces''' of the HVM and how they '''interact'''.<br />
<br />
<br />
==VM Data Structures==<br />
<br />
[[Image:Fmf_hvm_data_structures.png]]<br />
<br />
Green and red lines in this diagram indicate references from one item to another, although they are not drawn for every single reference (because that would be craaaaaaazy). <br />
<br />
The Script UDT block is particularly garish, because it is accommodating two different script types: user-defined scripts and named subroutines. Each component is colored differently depending on its use.<br />
<br />
<br />
=== What these do, top-down ===<br />
<br />
One way of learning how these data structures work is by discussing what each piece does on its own, and then assuming their combination. This is like studying one of those [http://www.applesource.com/peeler.html apple machines]; we say that this piece pierces the apple and holds it, that piece slices the skin off, and that blade cores the apple.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_top-down.png|link=FMF:The_HVM,_From_the_Top_Down]] || valign="top" | '''[[FMF:The_HVM,_From_the_Top_Down | Top-Down Description of the HVM]]'''<br>Read on for a detailed summary of the HVM's parts.<br />
|}<br />
<br />
<br />
<br />
===What These Do: Bottom-Up Description===<br />
<br />
If you've ever seen an [http://www.applesource.com/peeler.html apple machine] like the one just mentioned, you're probably aware that describing each element does absolutely nothing to help you understand how the thing works --you have to actually use it on an apple (preferably very slowly) to see how the magic is done. This is the essence of a bottom-up description: describe how certain parts '''interact''' at critical moments in the operation of the whole.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_bottom-up.png|link=FMF:The_HVM,_From_the_Bottom_Up]] || valign="top" | '''[[FMF:The_HVM,_From_the_Bottom_Up | Bottom-Up Description of the HVM]]'''<br>Read on for a detailed summary of how the HVM's parts interact.<br />
|}<br />
<br />
<br />
<br />
<br />
=HF Source Code & Formats=<br />
<br />
Strictly speaking, Henceforth has three different formats for its source code, and there is no such thing as "compiled Henceforth". These three formats are '''Format T''' (text-based), '''Format B''' (bytecode), and '''Format HF''' (compressed). Practically, most user code is generated in '''Format T''', which is then converted to '''Format B''' and compressed (to '''Format HF''') for storage. The HVM reads all three formats, but in doing so it converts T & HF to B. <br />
<br />
That said, we shall begin our study of Henceforth syntax by exhaustively reviewing all Henceforth commands in "source code" format ('''Format T'''), and then describe the mappings to other formats. A few commands cannot be explicitly represented in text format, as the following table details. We shall provide a recommended way of typing these when possible.<br />
<br />
{| cellspacing=0<br />
| &nbsp; || style="border: 1px solid black;" | '''Type''' || style="border: 1px solid black;" | '''Use'''<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format T || valign="top" style="border: 1px solid black;" | text || valign="top" style="border: 1px solid black;" | All human interaction (coding, discussing ideas, etc.) occurs in this format. Thins like script-local subroutines and function parameters cannot be expressed explicitly in this format.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format B || valign="top" style="border: 1px solid black;" | bytecode (binary) || valign="top" style="border: 1px solid black;" | The HVM executes Format B code. The HSP2HF cross-compiler outputs this format. Contains Henceforth Extensions for Scripting.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format HF || valign="top" style="border: 1px solid black;" | compressed || valign="top" style="border: 1px solid black;" | The XRPG specification requires all Format B code to be compressed into format HF lumps, which are then stored locally on the user's phone.<br />
|}<br />
<br />
Henceforth source is a rather slim language. Numbers and strings are supported, but concatenations like "Hello"+" world" and decimals like 2.4 are not. A syntax exists for "do" and "if" statements, and for declaring functions and variables. Several smaller hacks exist for efficiency's sake, as described earlier. And that's it. Anything else is either a "primitive" like '''add''', or a "block label", subject to the polymorphic lookup that characterizes Forth-like languages. Henceforth is not based explicitly on Classic Forth, but it hopes to learn from the mistakes of this peculiar family of languages. One of the main disadvantages to ANSI Forth is its pandering to ANSI C fixations, overcomplicating a language which, at its heart, always championed simplicity and elegance.<br />
<br />
<br />
==Format B Bytecode Specification (HFB)==<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_format-b.png|link=FMF:HFB]] || valign="top" | '''[[FMF:HFB | Format B Specification]]'''<br>Format B code is detailed into exhaustion on a separate page.<br />
|}<br />
<br />
<br />
<br />
<br />
==Henceforth Syntax Dictionary==<br />
<br />
This section contains all of the commands that you could enter into a run-time interpreter (if such a one existed). There is no reference to HSpeak scripts, GAME API calls, or anything else; this section simply explains Henceforth as a language.<br />
<br />
Think of it this way: A Henceforth "script" (called a '''subroutine''') is a slimmed-down version of a Henceforth HSpeak "script" (called a '''script'''). A script maintains a separate stack, for one thing, and script-local subroutines, for another. So, by way of example, calling "wait" within a subroutine would cause the HVM to crash. A script cannot be specified in textual format -that's only a recommended part of the specification, not required. A global script called "root" is used to load subroutines which would otherwise have no parent script.<br />
<br />
<br />
===Numbers===<br />
<br />
Numbers are represented by signed integers such as 2, 18, -35, or 43. When the HVM encounters a number, it pushes it to the stack. That's all for numbers, so let's cover stack effect diagrams briefly; they're necessary to understand more complicated features. Consider:<br />
{| border="1" cellpadding="3" rules="all"<br />
|-<br />
| <tt style="font-size:10pt">1 2 3</tt>|| <tt style="font-size:10pt">7</tt> || <tt style="font-size:10pt">1 2 3 7</tt><br />
|}<br />
<br />
(Note that stack-effect diagrams are oriented with the top of the stack on the right.)<br />
<br />
This diagram implies that the stack starts with the elements 1, 2, and 3 already in it, is then given the command "7", and the resulting stack is now 1, 2, 3, then 7. These is an example of the ''prototype'' style of learning; to teach you how to use a command, I give you a real-world example of how it works. <br />
<br />
If I want to write a stack effect diagram without the fancy table, I might use parentheses and the "--" symbol to mean "the operator we're talking about". For example: "if the interpreter encounters -5, the resulting stack-effect diagram would be <tt style="font-size:10pt">(1 2 3 -- 1 2 3 -5)</tt>". <br />
<br />
For convenience, hexadecimal numbers are allowed, with a '''0x''' prefix. Internally, however, they are simply stored as decimals.<br />
<br />
<br />
===Primitives===<br />
<br />
When the HVM interprets a literal string like '''exp''', '''mycode''', or '''abs''', it searches for the relevant subroutine. The primitives are exceptions to this; they are always executed immediately. A primitive consists of lowercase letters and underscores, and must contain at least one letter. We call this combination its '''symbol name'''.<br />
<br />
Here is a list of primitives, and their stack-effect diagrams:<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Primitive''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="5"| Stack Manipulation || <tt style="font-size:10pt">dup</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 3 )</tt> || "Duplicate" the top of the stack.<br />
|-<br />
| <tt style="font-size:10pt">swap</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 3 2 )</tt> || "Swap" the top two elements of the stack.<br />
|-<br />
| <tt style="font-size:10pt">drop</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 )</tt> || "Drop" the top element off the stack.<br />
|-<br />
| <tt style="font-size:10pt">over</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 2 )</tt> || Pull the penultimate element "over" the top element.<br />
|-<br />
| <tt style="font-size:10pt">rot</tt> || <tt style="font-size:10pt">( 1 2 3 4 -- 1 4 3 2 )</tt> || "Rotate" the top three elements of the stack.<br />
|-<br />
| rowspan="5"| Arithmetic || <tt style="font-size:10pt">add</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 5 )</tt> || Addition<br />
|-<br />
| <tt style="font-size:10pt">sub</tt> || <tt style="font-size:10pt">( 7 3 2 -- 7 1 )</tt> || Subtraction<br />
|-<br />
| <tt style="font-size:10pt">mult</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 6 )</tt> || Multiplication<br />
|-<br />
| <tt style="font-size:10pt">div</tt> || <tt style="font-size:10pt">( 1 7 3 -- 1 2 )</tt> || Division<br />
|-<br />
| <tt style="font-size:10pt">random</tt> || <tt style="font-size:10pt">( 1 4 8 -- 1 6 )</tt> || Push a random number to the stack.<br />
|-<br />
| rowspan="4"| Bitwise Operators || <tt style="font-size:10pt">b_xor</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 17 )</tt> || Exclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_or</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 53 )</tt> || Inclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_and</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 36 )</tt> || Bitwise "And"<br />
|-<br />
| <tt style="font-size:10pt">b_not</tt> || <tt style="font-size:10pt">( 1 52 -- 1 2147483595'''''**''''')</tt> || Bitwise "Not"<br />
|-<br />
| rowspan="6"| Logical Operators || <tt style="font-size:10pt">eq</tt> || <tt style="font-size:10pt">( 1 2 2 -- 1 1 )</tt> || Logical "Equals"<br />
|-<br />
| <tt style="font-size:10pt">lt</tt> || <tt style="font-size:10pt">( 1 5 2 -- 1 0 )</tt> || Logical "Less Than"<br />
|-<br />
| <tt style="font-size:10pt">not</tt> || <tt style="font-size:10pt">( 1 5 1 -- 1 5 0 )</tt> || Logical "Not"<br />
|-<br />
| <tt style="font-size:10pt">and</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 1 )</tt> || Logical "And"<br />
|-<br />
| <tt style="font-size:10pt">or</tt> || <tt style="font-size:10pt">( 1 5 0 1 -- 1 5 1 )</tt> || Inclusive Logical "Or"<br />
|-<br />
| <tt style="font-size:10pt">xor</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 0 )</tt> || Exclusive Logical "Or"<br />
|}<br />
'''''**'''''Please note: For now, the HVM doesn't use two's compliment to store integers. Hence, ~52 is masked with INT_MAX, which is 0x7FFFFFFF in 32-bit notation. We will probably change this for version 2.0, since people who use b_not (e.g., hackers) will be expecting the opposite behavior.<br />
<br />
<br />
There is no boolean type; the number zero is considered "false", and any other number is considered "true". For clarity, the number "1" is the preferred way to represent "true".<br />
<br />
The primitives "and" and "or" do ''not'' short-circuit. This is because a short-circuiting operator generates an ambiguous stack-effect diagram, and achieves no gain in performance or security (since it operates on values which have already been computed.) The [[HSP2HF | Cross-Compiler]] has an example of how to perform short-circuiting calculations in a manual way; see the section on Mathematical Functions.<br />
<br />
A number of additional keywords such as '''break''' and '''continue''' will be discussed in the section on If statements and Loops.<br />
<br />
===Blocks (Subroutines)===<br />
<br />
Labeled blocks, also called subroutines, are the core of Henceforth's modularization. They are strikingly similar to primitives, except that they can be defined, undefined, and overloaded. Labels, like primitives, are identified by a '''symbol name'''.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This creates a function called "mod", which performs a series of stack manipulations and basic arithmetic.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;7 3 mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Traditionally, we push the "parameters" to the subroutine in increasing order, and expect it to remove these arguments (and replace them with a return value, if any) during the course of its operation.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Overriding a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;drop drop 0<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;After executing this snippet, "mod" will have two definitions. Calling "mod" will result in the latter being evoked, which simply clears the parameters and returns zero.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Un-defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">/NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;/mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;If we execute the code from "Overriding a Subroutine", and then execute this code, "mod" will be left with a single definition: the first one.<br />
|}<br />
<br />
<br />
Any labeled block remains in effect even if the script which defined it finishes execution; make sure to un-define any subroutines you do not wish to linger. The HVM capitalizes on this behavior to limit the number of built-in primitives. The modulus operator described earlier is an example, as is "less than or equal to", which can be defined compositely. Here is a list of all the subroutines the HVM loads by default, with their stack-effect diagrams.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Subroutine''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="2"| Arithmetic || <tt style="font-size:10pt">exp</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 8 )</tt> || Compute x raised to the y ("exponent")<br />
|-<br />
| <tt style="font-size:10pt">mod</tt> || <tt style="font-size:10pt">( 1 10 4 -- 1 2 )</tt> || Calculate the "modulus"<br />
|-<br />
| rowspan="4"| Logical Operators || <tt style="font-size:10pt">neq</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 1 )</tt> || Logical "Not Equal To"<br />
|-<br />
| <tt style="font-size:10pt">lte</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 1 )</tt> || Logical "Less Than or Equal To"<br />
|-<br />
| <tt style="font-size:10pt">gt</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 0 )</tt> || Logical "Greater Than"<br />
|-<br />
| <tt style="font-size:10pt">gte</tt> || <tt style="font-size:10pt">( 1 63 45 -- 1 1 )</tt> || Logical "Greater Than or Equal To"<br />
|-<br />
| rowspan="2"| Convenience Methods || <tt style="font-size:10pt">true</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 45 52 1 )</tt> || Locus-of-control for "true"<br />
|-<br />
| <tt style="font-size:10pt">false</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 45 45 0 )</tt> || Locus-of-control for "false"<br />
|}<br />
<br />
<br />
===Variables===<br />
<br />
Shuffling the stack beyond the fourth element can get messy. As such, subroutines can allocate ''local variables'' to the scope of the parent '''script''' they are running in. Note that variables follow a similar naming convention to subroutines. They are accessed using the @VAR and VAR@ commands. You can remember them easily by putting an imaginary "stack" next to them: <br />
* '''(stack)@VAR''' takes a value from the stack and throws it '''at''' VAR. This is the same as setting VAR = pop().<br />
* '''VAR@(stack)''' takes the value in VAR and throws it '''at''' the stack. This is the same as pushing VAR.<br />
<br />
Here is some sample source for the '''lte''' function, written using variables. Note that, in reality, '''lte''' only uses the stack. This is much more efficient for the HVM, which only initializes the local variable store if it detects your script trying to access it.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using Variables'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\lte{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Pop the stack twice to get our local variables.<br>&nbsp;&nbsp;&nbsp;&nbsp;#We assume that we are testing @lhs <= @rhs<br>&nbsp;&nbsp;&nbsp;&nbsp;@rhs&nbsp;&nbsp;@lhs<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the first test (<) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res1<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;lt&nbsp;&nbsp;@res1<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the second test (==) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res2<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;eq&nbsp;&nbsp;@res2<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our two tests and do an or test. Leave it on<br>&nbsp;&nbsp;&nbsp;&nbsp;# the stack to be returned<br>&nbsp;&nbsp;&nbsp;&nbsp;res1@&nbsp;&nbsp;res2@&nbsp;&nbsp;or<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;As an exercise, you should try to convert this to a stack-based alternative. It should end up more concise, and also faster. (Profile it, to be sure).<br />
|}<br />
<br />
<br />
===Do/If & Loops===<br />
<br />
Most Forth-like languages have an "if" statement, and Henceforth is no exception. However, rather than encapsulating loops into labeled blocks (like Classic Forth), it adds another keyword: "do". This serves two purposes: first, it allows for better inlining than subroutines, and second, it provides a locus of control for jump commands. The second rationale is sometimes written as "otherwise, we'd have to use '''goto'''".<br />
<br />
Inside of a do loop, the commands '''break''' and '''continue''' also take effect; you should read [[HSZ#Id_11:_break | the HSZ documentation]] for an example of how these work conceptually. The mechanism for '''break''' and '''continue''' is different in the HVM, but the effect is the same.<br />
<br />
The standard Hamsterspeak engine also allows breaking out of multiple loops at once. In order to keep the syntax clean in Henceforth, we introduce two bytecodes for that purpose: '''break_x''' and '''continue_x'''. These take the element from the top of the stack, and break out of that many loops. The '''break''' command is effectively equivalent to "1 break_x". <br />
<br />
Thus, we add five more keywords to the list of Henceforth primitives: '''do''', '''if''', '''else''', '''break''', and '''continue'''. Brackets are used to denote code blocks where relevant. We will cover the syntax in simple examples. The first is a user-defined subroutine, "divide_safe", which avoids dividing by zero, and the second is an example of the Fibonacci sequence using "do" to achieve the effect of a "while" loop.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Save Divide-by-Zero Handling'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\divide_safe{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Do a zero-check on the divisor<br>&nbsp;&nbsp;&nbsp;&nbsp;dup&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;#Now, branch on that<br>&nbsp;&nbsp;&nbsp;&nbsp;if{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;drop&nbsp;&nbsp;drop&nbsp;&nbsp;0<br>&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;div<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Note that returning zero is just one way of handling the erroneous case. Also, take note of the two "drop" commands; these are required!<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Fibonacci Numbers'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\fibonacci{<br>&nbsp;&nbsp;&nbsp;&nbsp;@count&nbsp;&nbsp;&nbsp;&nbsp; #Must be >=1<br>&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;@fprev&nbsp;&nbsp;#fib(n-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;@fnow&nbsp;&nbsp; #fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Test-and-decrement<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;-1&nbsp;&nbsp;add&nbsp;&nbsp;@count<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Iterate & Accumulate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprev@&nbsp;&nbsp;fnow@&nbsp;&nbsp; add<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fnow@&nbsp;&nbsp; @fprev<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fnow<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Re-do the loop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fnow@<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This subroutine fails given zero as input. Modify it by adding a simple if statement before the main loop. <br />
|}<br />
<br />
This second example is far more interesting, since it answers the question "How do I Loop in Henceforth?"<br />
<br />
'''''A Note About Recursion''''': Although Henceforth fully supports recursion, the HVM is not designed for heavily-recursive functions. When possible, please use an iterative solution.<br />
<br />
==Henceforth Extensions for Scripting==<br />
<br />
The Henceforth specification recommends (read: ''does not require'') that the following pseudo-syntax be adopted to describe the commands which appear only in the '''Format B''' code for HSpeak script equivalents. A vanilla HVM should recognize these tokens and either ignore them, simulate them, or generate an informative error message when it encounters them.<br />
<br />
Backwards compatibility need not be preserved when converting between the '''Format B''' and '''Format T''' equivalents for script extensions.<br />
<br />
<br />
<br />
===HSpeak API Calls & User Scripts===<br />
<br />
The HVM has knowledge of most "built-in" functions that Hamsterspeak provides. These are called like any other subroutine, with one exception: most format converters create specialized subroutine calls out of them. In other words, if I write a call to '''wait_npc''' in my ('''Format T''') code, the converter will create ('''Format B''') source that points ''explicitly'' to the definition of '''wait_npc''' in the HVM; it is '''''not''''' possible to overload '''wait_npc'''. This breaks our promise of fully-interchangeable source formats, but it is hardly noticeable to most users, and keeps our global function lookup table from becoming too cluttered.<br />
<br />
See [[FMF:Plotdict]] for a listing of all API calls and their syntax in Henceforth.<br />
<br />
User-defined scripts are treated exactly like API calls; they are assigned IDs and called explicitly. <br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Calling an HSpeak API Method'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;12<br>&nbsp;&nbsp;show_txt</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Shows text box with ID 12. The api call '''show_txt''' is the shorthand for "show text box", [[FMF:Plotdict | as listed here]]. <br />
|}<br />
<br />
<br />
===Strings===<br />
<br />
Strings in Classic Forth are handled by simply pushing a series of integers (for each letter) to the stack. Eventually, Henceforth may go this route, but for now the API calls to strings are so simple that we chose a much simpler implementation:<br />
* Strings are a series of letters between double-quotes, like "Hello World", "Going?", or "Unicode: \u1000". As hinted by the last example, the HVM supports Unicode.<br />
* When a string is encountered (usually at the top of a script) it is pushed to the '''String Table''' for that script. Strings are pushed in order, cannot be removed or easily manipulated, and have IDs in increasing order.<br />
<br />
This is an ''ad hoc'' implementation of strings, until we get a clearer picture where the standard OHR's development is going with these ideas.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using a String'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;"Hello world!"<br>&nbsp;&nbsp;0 show_str</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Defining "Hello world!" will place this string at index zero; pushing 0 and then calling the API function '''show_str''' will reference that string explicitly.<br />
|}<br />
<br />
<br />
===Script-Local Subroutines===<br />
<br />
Here's where Henceforth starts to really get picky. When a Hamsterspeak script is converted to Henceforth, it is basically converted into a series of labeled blocks that call each other. The keen observer will note that representing these blocks as subroutines is inefficient, since they can be consistently enumerated. <br />
<br />
With that in mind, subroutines are allowed to define "script-local" subroutines which operate along a single thread of control within a named block. These are defined with a given ID, and called with an array-like syntax. They cannot be un-defined or overloaded, although theoretically they could be replaced (by re-defining them).<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\[ID]{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\[5]{<br>&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;plus<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Our "plus two" example from before, assigned to index 5.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">[ID]()</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;16&nbsp;&nbsp;[5]()</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This pushes 16, and calls local block 5. The result is a stack containing the number 18, if we define [5] as listed above. ''The parentheses, although not technically necessary, do a great deal to visually distinguish script-local subroutines from function parameters.''<br />
|}<br />
<br />
<br />
===Local Parameters & Return Values===<br />
<br />
'''''MAKE A NOTE: Give an example of how my_script{ exp } calls "exp" ON the stack of my_script. '''''<br />
<br />
An astute reader will have noticed that scripts currently have no way of passing values to other scripts. Calling "5 6 my_func" won't work, because '''my_func''' will get its own independent stack. This problem is circumvented through the use of local parameters, which also provide a slight speedup for small, frequently called subroutines. The local parameters are accessed by ID, with -1 a special ID that refers to the script's return value. <br />
* Executing @[5] will "pop the (parent) stack to local parameter 5"<br />
* Executing [5]@ will "push local parameter 5 onto the (current) stack<br />
For the return value, it's backwards:<br />
* Executing @[-1] will "pop the (current) stack to the return value"<br />
* Executing [-1]@ will "push the return value onto the (parent) stack", '''''unless''''' the return value is set to ''invalid'' (INT_MIN, for now).<br />
The only point of interest is that last bit. The problem is, the HSpeak command "return" sets the return value, and "exitscript" will exit and return that value. Some scripts simply don't set a return value, so the HVM pushes the '''most recently computed integer''' if it detects an ''invalid'' return value.<br />
<br />
===Comments===<br />
<br />
Henceforth supports comments, although it's important to remember that Formats B and HF will drop your comments; they can't be encoded. A comment begins with a hash mark (#) and ends with a line feed. <br />
<br />
<br />
===Global Variables===<br />
<br />
All that's left are global variables, which are manipulated with syntax like '''@[65.G]''' and '''[65.G]@'''. We realize this is a bit awkward, and we're open to a cleaner syntax (once we actually get a release going).<br />
<br />
<br />
<br />
= The Cross-Compiler =<br />
<br />
The OHRRPGCE FMF provides a tool to help convert existing Hamster Speak scripts to Henceforth lumps; read about it here:<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_hsp2hf.png|link=HSP2HF]] || valign="top" | '''[[HSP2HF | The HSP2HF Cross-Compiler]]'''<br>Read on for details about this tool.<br />
|}<br />
<br />
<br />
=Final Considerations=<br />
<br />
Henceforth is a big step away from Hamsterspeak, and it is best to approach it with an open mind. Try solving [http://projecteuler.net/index.php?section=problems common problems] with snippets of Henceforth; even if you simply write the solution on paper it will help you get used to thinking in a stack-based fashion. <br />
<br />
The developers of Henceforth are always interested in advancing the language; feel free to post a message [[User_talk:Sorlok_reaves | on Seth's page]] with your ideas --except, try to avoid buzzwords. Henceforth will never have objects, threading (which is ''different'' from multi-tasking), or static typing; it's just not that kinda language.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Henceforth_VM&diff=22309
Henceforth VM
2009-02-03T09:35:30Z
<p>Sorlok reaves: /* typo */</p>
<hr />
<div>{{FMF}}<br />
<br />
The Henceforth Virtual Machine (HVM, Henceforth VM) is used to dynamically interpret Henceforth (HF, Hamsterforth) scripts. The OHRRPGCE FMF does not use Hamsterspeak's traditional HSX/HSZ files; rather, it opts for a stack-based alternative. Henceforth files are stored with the .HF extension.<br />
<br />
<br />
<div style="border: 2px red solid; width: 600px; padding: 10px"><span style="color:#AA0000; font-weight:bold; font-size: 20pt">Warning: </span> This document is complete, except for the "final considerations" section, which will be trivial to add later.<br>We are currently in the process of reviewing the entire design specification, writing a toy interpreter, and polishing up (and segmenting) this document. <br>We'll gladly take your suggestions on the "Discussion" page, but please do not modify, distribute, or trust this document. It will soon be as open to the community as everything else on this Wiki, but it'll take a few weeks to do the final review.<br><br>Thanks~<br>/Seth<br />
</div><br />
<br />
<br />
=Acknowledgements=<br />
<br />
A big thanks to both James and Ralph for their well-founded suggestions regarding the HVM, and to Mike for clarifying countless issues. If you start reading into the minute details of the HVM, you'll find that a whole lot of it is lean, clean, and sensible. This is almost entirely due to the suggestions and experience of the OHR's patriarchs, which I truly appreciate. <br />
<br />
<br />
=Introduction to Henceforth=<br />
<br />
Henceforth is a simple, stack-based programming language which the OHRRPGCE FMF uses as a crossover language from traditional Hamsterspeak. Like all of the lumps exclusive to this project, .[[HF]] lumps containing Henceforth code are cross-compiled automatically for you when you convert your game to XRPG Format. However, all developers should consider learning basic Henceforth syntax to aid them in writing run-time debug scripts and compile-time optimized versions of their most time-critical Hamsterspeak scripts. <br />
<br />
Henceforth is a stack-based language, designed to obviate register allocation problems and minimize script size. You should read James's [[How_does_plotscripting_work%3F#The_Stack | excellent analogy of a stack]], if you aren't familiar with the concept. Hamsterspeak uses the stack for ordering scripts; Henceforth, however, uses the stack for storing data.<br />
<br />
<br />
==A Henceforth Primer==<br />
<br />
Although Henceforth is not a compiled language, you can pretend that "source" Henceforth is compiled into "binary" HFZ lumps, which the game engine runs, and only [[User:Sorlok_reaves | one person]] will cringe and correct you. So let's have a look at some Henceforth source code, shall we? Any text after a [http://dictionary.reference.com/browse/hash%20mark hash mark] until the next newline is considered a comment, and [http://dictionary.reference.com/browse/whitespace whitespace] is used to separate commands. Consider the following script.<br />
#One way to separate commands is by hitting "Enter" after each command.<br />
4 #Push 4<br />
5 #Push 5<br />
add #Pop the top two elements and add them. Push the result.<br />
@result #Pop the top element and store it in the variable "result".<br />
<br />
#Another way to separate commands is using spaces.<br />
4 5 add @result<br />
Most programmers will use a mix of spaces and newlines to keep their scripts clear and sensible. For example, "4 5 add" should probably always be on the same line. Regardless of what format you use, manipulation of the stack is the same. The following diagram shows how the stack (and the variables list) changes as the HVM reads each command, from left to right.<br />
<br />
[[Image:Fmf_stack_diagram.png]]<br />
<br />
<br />
==The "Pure" Virtual Machine==<br />
<br />
From a theoretical point of view, Henceforth does not require a location to store variables. Rather, we could simply add '''block labels''' and a delayed execution ('''prepending''') command:<br />
* Surrounding some source with '''\name{}''' will label that block with "name"; if '''name''' later appears as a command, this block will be inserted in-place. Later writing '''/name''' will un-define "name". If multiple blocks are labeled similarly, the most-recent label is considered valid. <br />
* Pre-pending "&" to a command will push the result of that command ''into the source code itself'', at the time that command is first encountered. <br />
<br />
'''Block labels''' allow functions to be written very naturally:<br />
#The "modulo" function expects two arguments to be<br />
# on the stack; it transforms (X Y) to (X%Y)<br />
\modulo {<br />
swap dup rot dup rot swap #Now we have (X Y X Y)<br />
div mult sub #The heart of our algorithm<br />
}<br />
There's no need to "return" anything; the labeled block operates in a way that leaves one item on the stack. Here's some code to use this block as a function:<br />
#We want to compute 7%3<br />
7 3 modulo #After this, "1" is left on the stack<br />
<br />
This is, in fact, the exact syntax for writing a function in Henceforth. Now, let's cover '''prepending''':<br />
#Avoiding variables with named blocks.<br />
4 5 add <br />
\result{ #Label the start of the "result" block<br />
&pop #This is tricky: the first time we encounter this<br />
# (i.e., when "\result" is defined), it executes "pop",<br />
# resulting in "9", which is pushed into the source.<br />
} #Closes the "\result" label.<br />
<br />
result #Calling "result" will now push "9".<br />
<br />
Unlike named blocks, prepending is not actually implemented in Henceforth; you can ignore that last bit of code if you don't understand it. Which brings us to our main point:<br />
<br />
<big><big style="color:green;"><br />
'''Henceforth was designed around a "pure" VM'''</big><big>, but it was then modified to </big><big style="color:green;">'''run efficiently'''</big><big> on a phone, and to </big><big style="color:green;">'''allow an easy mapping'''</big><big> from existing Hamsterspeak scripts. <br />
</big></big><br />
<br />
It is crucial that you keep the ideal virtual machine in mind, or else you will start asking about "Henceforth's registers" (which don't exist) or you'll start using variables way too much. You also need to be cognizant of the dirty details required to kludge Hamsterspeak onto a phone; otherwise, things like script-level subroutines and our extravagant caching mechanism will seem redundant and senseless.<br />
<br />
(Note to readers: At this point, if you're only interested in the Henceforth language, you can skip down to the section on [[Henceforth_VM#Henceforth Syntax Dictionary | Henceforth Syntax]].)<br />
<br />
<br />
==Where the HVM Fits Into the Game Loop==<br />
<br />
Let's pretend that on a certain map in your game, a certain NPC is set up to "run script 4" when the user walks up to it and presses "Accept". Upon pressing "Accept", the game engine checks if this NPC has a script ID assigned to its "accept" behavior. The classic OHR performs this same task. If one exists, it is initialized and cached. At this point, the game engine continues with its tasks for that cycle (e.g., message boxes, vehicles). This (including user input) constitutes the "Update" phase of the game loop. Following this is the "Paint" phase, which shows one frame's worth of updates to the user. After painting, the tick count is incremented, and the loop repeats. The first thing that happens then is the "scripts" phase, during which scripts are actually executed (see below). The loop repeats indefinitely.<br />
<br />
[[Image:Hf_flowchart.png]]<br />
<br />
<br />
<br />
===Some Technical Considerations===<br />
<br />
When a script is triggered by any means, it suspends the currently-executing script; see "The Stack" in "[[How does plotscripting work?]]". This makes sense; if your script calls another script, one expects the new script to take over. It also means that if you step on several map tiles which each have (long) scripts, the last maptile's script will finish first. (Threading will probably change this.)<br />
<br />
But wait, there's more! Several small but important details of script initialization include:<br />
# If you trigger the same script twice (e.g., by stepping on two copies of an NPC one after the other) the script loader will detect that you are trying to load a script with the '''same id''' as the currently executing script. If the "Permit double-triggering of scripts" general bitset is on (or if the script is explicitly called by ''another script'') then this is allowed, and a second instance of the script is initialized. <br />
# When a script instance is '''first''' loaded, its "delay" counter is set to 2 ticks. This is ''not'' the same thing as instructing the script to "wait", because it remains the active script while delaying. If another script pre-empts this one, the delay counter does not decrement until the new script finishes. (When the new script finishes, the old one immediately resumes what it was doing, be it running or decrementing the "delay" counter.) <br />
# To prepare for parallel scripts, the active script is pushed to the top of the "waiting" stack, and is manipulated by a reference ID. This way, all scripts are kept in one place, and a future, parallel version of GAME_FMF can just scan through this list once each cycle, updating all scripts until they enter a waiting state.<br />
<br />
<br />
<br />
<br />
=Virtual Hardware Specification=<br />
<br />
The [http://en.wikipedia.org/wiki/Virtual_machine Wikipedia page] for Virtual Machines focuses on their use in emulating existing hardware. In fact, there is no hardware version of the HVM, just like there is no hardware equivalent of the Java Virtual Machine. Rather, Henceforth defines an unambiguous virtual hardware specification which can be ported to any platform with guaranteed interoperability. Currently, the only platform with a working HVM is the OHRRPGCEFMF, due to the bias of the HVM's design towards mobile devices.<br />
<br />
Anything in the world can be described by its nouns and verbs --the things it '''has''' and what they '''do'''. So let's inspect the '''pieces''' of the HVM and how they '''interact'''.<br />
<br />
<br />
==VM Data Structures==<br />
<br />
[[Image:Fmf_hvm_data_structures.png]]<br />
<br />
Green and red lines in this diagram indicate references from one item to another, although they are not drawn for every single reference (because that would be craaaaaaazy). <br />
<br />
The Script UDT block is particularly garish, because it is accommodating two different script types: user-defined scripts and named subroutines. Each component is colored differently depending on its use.<br />
<br />
<br />
=== What these do, top-down ===<br />
<br />
One way of learning how these data structures work is by discussing what each piece does on its own, and then assuming their combination. This is like studying one of those [http://www.applesource.com/peeler.html apple machines]; we say that this piece pierces the apple and holds it, that piece slices the skin off, and that blade cores the apple.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_top-down.png|link=FMF:The_HVM,_From_the_Top_Down]] || valign="top" | '''[[FMF:The_HVM,_From_the_Top_Down | Top-Down Description of the HVM]]'''<br>Read on for a detailed summary of the HVM's parts.<br />
|}<br />
<br />
<br />
<br />
===What These Do: Bottom-Up Description===<br />
<br />
If you've ever seen an [http://www.applesource.com/peeler.html apple machine] like the one just mentioned, you're probably aware that describing each element does absolutely nothing to help you understand how the thing works --you have to actually use it on an apple (preferably very slowly) to see how the magic is done. This is the essence of a bottom-up description: describe how certain parts '''interact''' at critical moments in the operation of the whole.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_bottom-up.png|link=FMF:The_HVM,_From_the_Bottom_Up]] || valign="top" | '''[[FMF:The_HVM,_From_the_Bottom_Up | Bottom-Up Description of the HVM]]'''<br>Read on for a detailed summary of how the HVM's parts interact.<br />
|}<br />
<br />
<br />
<br />
<br />
=HF Source Code & Formats=<br />
<br />
Strictly speaking, Henceforth has three different formats for its source code, and there is no such thing as "compiled Henceforth". These three formats are '''Format T''' (text-based), '''Format B''' (bytecode), and '''Format HF''' (compressed). Practically, most user code is generated in '''Format T''', which is then converted to '''Format B''' and compressed (to '''Format HF''') for storage. The HVM reads all three formats, but in doing so it converts T & HF to B. <br />
<br />
That said, we shall begin our study of Henceforth syntax by exhaustively reviewing all Henceforth commands in "source code" format ('''Format T'''), and then describe the mappings to other formats. A few commands cannot be explicitly represented in text format, as the following table details. We shall provide a recommended way of typing these when possible.<br />
<br />
{| cellspacing=0<br />
| &nbsp; || style="border: 1px solid black;" | '''Type''' || style="border: 1px solid black;" | '''Use'''<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format T || valign="top" style="border: 1px solid black;" | text || valign="top" style="border: 1px solid black;" | All human interaction (coding, discussing ideas, etc.) occurs in this format. Thins like script-local subroutines and function parameters cannot be expressed explicitly in this format.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format B || valign="top" style="border: 1px solid black;" | bytecode (binary) || valign="top" style="border: 1px solid black;" | The HVM executes Format B code. The HSP2HF cross-compiler outputs this format. Contains Henceforth Extensions for Scripting.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format HF || valign="top" style="border: 1px solid black;" | compressed || valign="top" style="border: 1px solid black;" | The XRPG specification requires all Format B code to be compressed into format HF lumps, which are then stored locally on the user's phone.<br />
|}<br />
<br />
Henceforth source is a rather slim language. Numbers and strings are supported, but concatenations like "Hello"+" world" and decimals like 2.4 are not. A syntax exists for "do" and "if" statements, and for declaring functions and variables. Several smaller hacks exist for efficiency's sake, as described earlier. And that's it. Anything else is either a "primitive" like '''add''', or a "block label", subject to the polymorphic lookup that characterizes Forth-like languages. Henceforth is not based explicitly on Classic Forth, but it hopes to learn from the mistakes of this peculiar family of languages. One of the main disadvantages to ANSI Forth is its pandering to ANSI C fixations, overcomplicating a language which, at its heart, always championed simplicity and elegance.<br />
<br />
<br />
==Format B Bytecode Specification (HFB)==<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_format-b.png|link=FMF:HFB]] || valign="top" | '''[[FMF:HFB | Format B Specification]]'''<br>Format B code is detailed into exhaustion on a separate page.<br />
|}<br />
<br />
<br />
<br />
<br />
==Henceforth Syntax Dictionary==<br />
<br />
This section contains all of the commands that you could enter into a run-time interpreter (if such a one existed). There is no reference to HSpeak scripts, GAME API calls, or anything else; this section simply explains Henceforth as a language.<br />
<br />
Think of it this way: A Henceforth "script" (called a '''subroutine''') is a slimmed-down version of a Henceforth HSpeak "script" (called a '''script'''). A script maintains a separate stack, for one thing, and script-local subroutines, for another. So, by way of example, calling "wait" within a subroutine would cause the HVM to crash. A script cannot be specified in textual format -that's only a recommended part of the specification, not required. A global script called "root" is used to load subroutines which would otherwise have no parent script.<br />
<br />
<br />
===Numbers===<br />
<br />
Numbers are represented by signed integers such as 2, 18, -35, or 43. When the HVM encounters a number, it pushes it to the stack. That's all for numbers, so let's cover stack effect diagrams briefly; they're necessary to understand more complicated features. Consider:<br />
{| border="1" cellpadding="3" rules="all"<br />
|-<br />
| <tt style="font-size:10pt">1 2 3</tt>|| <tt style="font-size:10pt">7</tt> || <tt style="font-size:10pt">1 2 3 7</tt><br />
|}<br />
<br />
(Note that stack-effect diagrams are oriented with the top of the stack on the right.)<br />
<br />
This diagram implies that the stack starts with the elements 1, 2, and 3 already in it, is then given the command "7", and the resulting stack is now 1, 2, 3, then 7. These is an example of the ''prototype'' style of learning; to teach you how to use a command, I give you a real-world example of how it works. <br />
<br />
If I want to write a stack effect diagram without the fancy table, I might use parentheses and the "--" symbol to mean "the operator we're talking about". For example: "if the interpreter encounters -5, the resulting stack-effect diagram would be <tt style="font-size:10pt">(1 2 3 -- 1 2 3 -5)</tt>". <br />
<br />
For convenience, hexadecimal numbers are allowed, with a '''0x''' prefix. Internally, however, they are simply stored as decimals.<br />
<br />
<br />
===Primitives===<br />
<br />
When the HVM interprets a literal string like '''exp''', '''mycode''', or '''abs''', it searches for the relevant subroutine. The primitives are exceptions to this; they are always executed immediately. A primitive consists of lowercase letters and underscores, and must contain at least one letter. We call this combination its '''symbol name'''.<br />
<br />
Here is a list of primitives, and their stack-effect diagrams:<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Primitive''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="5"| Stack Manipulation || <tt style="font-size:10pt">dup</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 3 )</tt> || "Duplicate" the top of the stack.<br />
|-<br />
| <tt style="font-size:10pt">swap</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 3 2 )</tt> || "Swap" the top two elements of the stack.<br />
|-<br />
| <tt style="font-size:10pt">drop</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 )</tt> || "Drop" the top element off the stack.<br />
|-<br />
| <tt style="font-size:10pt">over</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 2 )</tt> || Pull the penultimate element "over" the top element.<br />
|-<br />
| <tt style="font-size:10pt">rot</tt> || <tt style="font-size:10pt">( 1 2 3 4 -- 1 4 3 2 )</tt> || "Rotate" the top three elements of the stack.<br />
|-<br />
| rowspan="5"| Arithmetic || <tt style="font-size:10pt">add</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 5 )</tt> || Addition<br />
|-<br />
| <tt style="font-size:10pt">sub</tt> || <tt style="font-size:10pt">( 7 3 2 -- 7 1 )</tt> || Subtraction<br />
|-<br />
| <tt style="font-size:10pt">mult</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 6 )</tt> || Multiplication<br />
|-<br />
| <tt style="font-size:10pt">div</tt> || <tt style="font-size:10pt">( 1 7 3 -- 1 2 )</tt> || Division<br />
|-<br />
| <tt style="font-size:10pt">random</tt> || <tt style="font-size:10pt">( 1 4 8 -- 1 6 )</tt> || Push a random number to the stack.<br />
|-<br />
| rowspan="4"| Bitwise Operators || <tt style="font-size:10pt">b_xor</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 17 )</tt> || Exclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_or</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 53 )</tt> || Inclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_and</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 36 )</tt> || Bitwise "And"<br />
|-<br />
| <tt style="font-size:10pt">b_not</tt> || <tt style="font-size:10pt">( 1 52 -- 1 2147483595'''''**''''')</tt> || Bitwise "Not"<br />
|-<br />
| rowspan="6"| Logical Operators || <tt style="font-size:10pt">eq</tt> || <tt style="font-size:10pt">( 1 2 2 -- 1 1 )</tt> || Logical "Equals"<br />
|-<br />
| <tt style="font-size:10pt">lt</tt> || <tt style="font-size:10pt">( 1 5 2 -- 1 0 )</tt> || Logical "Less Than"<br />
|-<br />
| <tt style="font-size:10pt">not</tt> || <tt style="font-size:10pt">( 1 5 1 -- 1 5 0 )</tt> || Logical "Not"<br />
|-<br />
| <tt style="font-size:10pt">and</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 1 )</tt> || Logical "And"<br />
|-<br />
| <tt style="font-size:10pt">or</tt> || <tt style="font-size:10pt">( 1 5 0 1 -- 1 5 1 )</tt> || Inclusive Logical "Or"<br />
|-<br />
| <tt style="font-size:10pt">xor</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 0 )</tt> || Exclusive Logical "Or"<br />
|}<br />
'''''**'''''Please note: For now, the HVM doesn't use two's compliment to store integers. Hence, ~52 is masked with INT_MAX, which is 0x7FFFFFFF in 32-bit notation. We will probably change this for version 2.0, since people who use b_not (e.g., hackers) will be expecting the opposite behavior.<br />
<br />
<br />
There is no boolean type; the number zero is considered "false", and any other number is considered "true". For clarity, the number "1" is the preferred way to represent "true".<br />
<br />
The primitives "and" and "or" do ''not'' short-circuit. This is because a short-circuiting operator generates an ambiguous stack-effect diagram, and achieves no gain in performance or security (since it operates on values which have already been computed.) The [[HSP2HF | Cross-Compiler]] has an example of how to perform short-circuiting calculations in a manual way; see the section on Mathematical Functions.<br />
<br />
A number of additional keywords such as '''break''' and '''continue''' will be discussed in the section on If statements and Loops.<br />
<br />
===Blocks (Subroutines)===<br />
<br />
Labeled blocks, also called subroutines, are the core of Henceforth's modularization. They are strikingly similar to primitives, except that they can be defined, undefined, and overloaded. Labels, like primitives, are identified by a '''symbol name'''.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This creates a function called "mod", which performs a series of stack manipulations and basic arithmetic.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;7 3 mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Traditionally, we push the "parameters" to the subroutine in increasing order, and expect it to remove these arguments (and replace them with a return value, if any) during the course of its operation.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Overriding a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;drop drop 0<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;After executing this snippet, "mod" will have two definitions. Calling "mod" will result in the latter being evoked, which simply clears the parameters and returns zero.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Un-defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">/NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;/mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;If we execute the code from "Overriding a Subroutine", and then execute this code, "mod" will be left with a single definition: the first one.<br />
|}<br />
<br />
<br />
Any labeled block remains in effect even if the script which defined it finishes execution; make sure to un-define any subroutines you do not wish to linger. The HVM capitalizes on this behavior to limit the number of built-in primitives. The modulus operator described earlier is an example, as is "less than or equal to", which can be defined compositely. Here is a list of all the subroutines the HVM loads by default, with their stack-effect diagrams.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Subroutine''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="2"| Arithmetic || <tt style="font-size:10pt">exp</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 8 )</tt> || Compute x raised to the y ("exponent")<br />
|-<br />
| <tt style="font-size:10pt">mod</tt> || <tt style="font-size:10pt">( 1 10 4 -- 1 2 )</tt> || Calculate the "modulus"<br />
|-<br />
| rowspan="4"| Logical Operators || <tt style="font-size:10pt">neq</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 1 )</tt> || Logical "Not Equal To"<br />
|-<br />
| <tt style="font-size:10pt">lte</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 1 )</tt> || Logical "Less Than or Equal To"<br />
|-<br />
| <tt style="font-size:10pt">gt</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 0 )</tt> || Logical "Greater Than"<br />
|-<br />
| <tt style="font-size:10pt">gte</tt> || <tt style="font-size:10pt">( 1 63 45 -- 1 1 )</tt> || Logical "Greater Than or Equal To"<br />
|-<br />
| rowspan="2"| Convenience Methods || <tt style="font-size:10pt">true</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 45 52 1 )</tt> || Locus-of-control for "true"<br />
|-<br />
| <tt style="font-size:10pt">false</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 45 45 0 )</tt> || Locus-of-control for "false"<br />
|}<br />
<br />
<br />
===Variables===<br />
<br />
Shuffling the stack beyond the fourth element can get messy. As such, subroutines can allocate ''local variables'' to the scope of the parent '''script''' they are running in. Note that variables follow a similar naming convention to subroutines. They are accessed using the @VAR and VAR@ commands. You can remember them easily by putting an imaginary "stack" next to them: <br />
* '''(stack)@VAR''' takes a value from the stack and throws it '''at''' VAR. This is the same as setting VAR = pop().<br />
* '''VAR@(stack)''' takes the value in VAR and throws it '''at''' the stack. This is the same as pushing VAR.<br />
<br />
Here is some sample source for the '''lte''' function, written using variables. Note that, in reality, '''lte''' only uses the stack. This is much more efficient for the HVM, which only initializes the local variable store if it detects your script trying to access it.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using Variables'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\lte{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Pop the stack twice to get our local variables.<br>&nbsp;&nbsp;&nbsp;&nbsp;#We assume that we are testing @lhs <= @rhs<br>&nbsp;&nbsp;&nbsp;&nbsp;@rhs&nbsp;&nbsp;@lhs<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the first test (<) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res1<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;lt&nbsp;&nbsp;@res1<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the second test (==) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res2<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;eq&nbsp;&nbsp;@res2<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our two tests and do an or test. Leave it on<br>&nbsp;&nbsp;&nbsp;&nbsp;# the stack to be returned<br>&nbsp;&nbsp;&nbsp;&nbsp;res1@&nbsp;&nbsp;res2@&nbsp;&nbsp;or<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;As an exercise, you should try to convert this to a stack-based alternative. It should end up more concise, and also faster. (Profile it, to be sure).<br />
|}<br />
<br />
<br />
===Do/If & Loops===<br />
<br />
Most Forth-like languages have an "if" statement, and Henceforth is no exception. However, rather than encapsulating loops into labeled blocks (like Classic Forth), it adds another keyword: "do". This serves two purposes: first, it allows for better inlining than subroutines, and second, it provides a locus of control for jump commands. The second rationale is sometimes written as "otherwise, we'd have to use '''goto'''".<br />
<br />
Inside of a do loop, the commands '''break''' and '''continue''' also take effect; you should read [[HSZ#Id_11:_break | the HSZ documentation]] for an example of how these work conceptually. The mechanism for '''break''' and '''continue''' is different in the HVM, but the effect is the same.<br />
<br />
The standard Hamsterspeak engine also allows breaking out of multiple loops at once. In order to keep the syntax clean in Henceforth, we introduce two bytecodes for that purpose: '''break_x''' and '''continue_x'''. These take the element from the top of the stack, and break out of that many loops. The '''break''' command is effectively equivalent to "1 break_x". <br />
<br />
Thus, we add five more keywords to the list of Henceforth primitives: '''do''', '''if''', '''else''', '''break''', and '''continue'''. Brackets are used to denote code blocks where relevant. We will cover the syntax in simple examples. The first is a user-defined subroutine, "divide_safe", which avoids dividing by zero, and the second is an example of the Fibonacci sequence using "do" to achieve the effect of a "while" loop.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Save Divide-by-Zero Handling'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\divide_safe{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Do a zero-check on the divisor<br>&nbsp;&nbsp;&nbsp;&nbsp;dup&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;#Now, branch on that<br>&nbsp;&nbsp;&nbsp;&nbsp;if{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pop&nbsp;&nbsp;pop&nbsp;&nbsp;0<br>&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;div<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Note that returning zero is just one way of handling the erroneous case. Also, take note of the two "pop" commands; these are required!<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Fibonacci Numbers'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\fibonacci{<br>&nbsp;&nbsp;&nbsp;&nbsp;@count&nbsp;&nbsp;&nbsp;&nbsp; #Must be >=1<br>&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;@fprev&nbsp;&nbsp;#fib(n-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;@fnow&nbsp;&nbsp; #fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Test-and-decrement<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;-1&nbsp;&nbsp;add&nbsp;&nbsp;@count<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Iterate & Accumulate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprev@&nbsp;&nbsp;fnow@&nbsp;&nbsp; add<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fnow@&nbsp;&nbsp; @fprev<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fnow<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Re-do the loop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fnow@<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This subroutine fails given zero as input. Modify it by adding a simple if statement before the main loop. <br />
|}<br />
<br />
This second example is far more interesting, since it answers the question "How do I Loop in Henceforth?"<br />
<br />
'''''A Note About Recursion''''': Although Henceforth fully supports recursion, the HVM is not designed for heavily-recursive functions. When possible, please use an iterative solution.<br />
<br />
<br />
<br />
==Henceforth Extensions for Scripting==<br />
<br />
The Henceforth specification recommends (read: ''does not require'') that the following pseudo-syntax be adopted to describe the commands which appear only in the '''Format B''' code for HSpeak script equivalents. A vanilla HVM should recognize these tokens and either ignore them, simulate them, or generate an informative error message when it encounters them.<br />
<br />
Backwards compatibility need not be preserved when converting between the '''Format B''' and '''Format T''' equivalents for script extensions.<br />
<br />
<br />
<br />
===HSpeak API Calls & User Scripts===<br />
<br />
The HVM has knowledge of most "built-in" functions that Hamsterspeak provides. These are called like any other subroutine, with one exception: most format converters create specialized subroutine calls out of them. In other words, if I write a call to '''wait_npc''' in my ('''Format T''') code, the converter will create ('''Format B''') source that points ''explicitly'' to the definition of '''wait_npc''' in the HVM; it is '''''not''''' possible to overload '''wait_npc'''. This breaks our promise of fully-interchangeable source formats, but it is hardly noticeable to most users, and keeps our global function lookup table from becoming too cluttered.<br />
<br />
See [[FMF:Plotdict]] for a listing of all API calls and their syntax in Henceforth.<br />
<br />
User-defined scripts are treated exactly like API calls; they are assigned IDs and called explicitly. <br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Calling an HSpeak API Method'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;12<br>&nbsp;&nbsp;show_txt</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Shows text box with ID 12. The api call '''show_txt''' is the shorthand for "show text box", [[FMF:Plotdict | as listed here]]. <br />
|}<br />
<br />
<br />
===Strings===<br />
<br />
Strings in Classic Forth are handled by simply pushing a series of integers (for each letter) to the stack. Eventually, Henceforth may go this route, but for now the API calls to strings are so simple that we chose a much simpler implementation:<br />
* Strings are a series of letters between double-quotes, like "Hello World", "Going?", or "Unicode: \u1000". As hinted by the last example, the HVM supports Unicode.<br />
* When a string is encountered (usually at the top of a script) it is pushed to the '''String Table''' for that script. Strings are pushed in order, cannot be removed or easily manipulated, and have IDs in increasing order.<br />
<br />
This is an ''ad hoc'' implementation of strings, until we get a clearer picture where the standard OHR's development is going with these ideas.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using a String'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;"Hello world!"<br>&nbsp;&nbsp;0 show_str</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Defining "Hello world!" will place this string at index zero; pushing 0 and then calling the API function '''show_str''' will reference that string explicitly.<br />
|}<br />
<br />
<br />
===Script-Local Subroutines===<br />
<br />
Here's where Henceforth starts to really get picky. When a Hamsterspeak script is converted to Henceforth, it is basically converted into a series of labeled blocks that call each other. The keen observer will note that representing these blocks as subroutines is inefficient, since they can be consistently enumerated. <br />
<br />
With that in mind, subroutines are allowed to define "script-local" subroutines which operate along a single thread of control within a named block. These are defined with a given ID, and called with an array-like syntax. They cannot be un-defined or overloaded, although theoretically they could be replaced (by re-defining them).<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\[ID]{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\[5]{<br>&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;plus<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Our "plus two" example from before, assigned to index 5.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">[ID]()</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;16&nbsp;&nbsp;[5]()</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This pushes 16, and calls local block 5. The result is a stack containing the number 18, if we define [5] as listed above. ''The parentheses, although not technically necessary, do a great deal to visually distinguish script-local subroutines from function parameters.''<br />
|}<br />
<br />
<br />
===Local Parameters & Return Values===<br />
<br />
'''''MAKE A NOTE: Give an example of how my_script{ exp } calls "exp" ON the stack of my_script. '''''<br />
<br />
An astute reader will have noticed that scripts currently have no way of passing values to other scripts. Calling "5 6 my_func" won't work, because '''my_func''' will get its own independent stack. This problem is circumvented through the use of local parameters, which also provide a slight speedup for small, frequently called subroutines. The local parameters are accessed by ID, with -1 a special ID that refers to the script's return value. <br />
* Executing @[5] will "pop the (parent) stack to local parameter 5"<br />
* Executing [5]@ will "push local parameter 5 onto the (current) stack<br />
For the return value, it's backwards:<br />
* Executing @[-1] will "pop the (current) stack to the return value"<br />
* Executing [-1]@ will "push the return value onto the (parent) stack", '''''unless''''' the return value is set to ''invalid'' (INT_MIN, for now).<br />
The only point of interest is that last bit. The problem is, the HSpeak command "return" sets the return value, and "exitscript" will exit and return that value. Some scripts simply don't set a return value, so the HVM pushes the '''most recently computed integer''' if it detects an ''invalid'' return value.<br />
<br />
===Comments===<br />
<br />
Henceforth supports comments, although it's important to remember that Formats B and HF will drop your comments; they can't be encoded. A comment begins with a hash mark (#) and ends with a line feed. <br />
<br />
<br />
===Global Variables===<br />
<br />
All that's left are global variables, which are manipulated with syntax like '''@[65.G]''' and '''[65.G]@'''. We realize this is a bit awkward, and we're open to a cleaner syntax (once we actually get a release going).<br />
<br />
<br />
<br />
= The Cross-Compiler =<br />
<br />
The OHRRPGCE FMF provides a tool to help convert existing Hamster Speak scripts to Henceforth lumps; read about it here:<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_hsp2hf.png|link=HSP2HF]] || valign="top" | '''[[HSP2HF | The HSP2HF Cross-Compiler]]'''<br>Read on for details about this tool.<br />
|}<br />
<br />
<br />
=Final Considerations=<br />
<br />
Henceforth is a big step away from Hamsterspeak, and it is best to approach it with an open mind. Try solving [http://projecteuler.net/index.php?section=problems common problems] with snippets of Henceforth; even if you simply write the solution on paper it will help you get used to thinking in a stack-based fashion. <br />
<br />
The developers of Henceforth are always interested in advancing the language; feel free to post a message [[User_talk:Sorlok_reaves | on Seth's page]] with your ideas --except, try to avoid buzzwords. Henceforth will never have objects, threading (which is ''different'' from multi-tasking), or static typing; it's just not that kinda language.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Henceforth_VM&diff=22308
Henceforth VM
2009-02-03T09:34:51Z
<p>Sorlok reaves: /* Tiny bug; will remain until 2.0. */</p>
<hr />
<div>{{FMF}}<br />
<br />
The Henceforth Virtual Machine (HVM, Henceforth VM) is used to dynamically interpret Henceforth (HF, Hamsterforth) scripts. The OHRRPGCE FMF does not use Hamsterspeak's traditional HSX/HSZ files; rather, it opts for a stack-based alternative. Henceforth files are stored with the .HF extension.<br />
<br />
<br />
<div style="border: 2px red solid; width: 600px; padding: 10px"><span style="color:#AA0000; font-weight:bold; font-size: 20pt">Warning: </span> This document is complete, except for the "final considerations" section, which will be trivial to add later.<br>We are currently in the process of reviewing the entire design specification, writing a toy interpreter, and polishing up (and segmenting) this document. <br>We'll gladly take your suggestions on the "Discussion" page, but please do not modify, distribute, or trust this document. It will soon be as open to the community as everything else on this Wiki, but it'll take a few weeks to do the final review.<br><br>Thanks~<br>/Seth<br />
</div><br />
<br />
<br />
=Acknowledgements=<br />
<br />
A big thanks to both James and Ralph for their well-founded suggestions regarding the HVM, and to Mike for clarifying countless issues. If you start reading into the minute details of the HVM, you'll find that a whole lot of it is lean, clean, and sensible. This is almost entirely due to the suggestions and experience of the OHR's patriarchs, which I truly appreciate. <br />
<br />
<br />
=Introduction to Henceforth=<br />
<br />
Henceforth is a simple, stack-based programming language which the OHRRPGCE FMF uses as a crossover language from traditional Hamsterspeak. Like all of the lumps exclusive to this project, .[[HF]] lumps containing Henceforth code are cross-compiled automatically for you when you convert your game to XRPG Format. However, all developers should consider learning basic Henceforth syntax to aid them in writing run-time debug scripts and compile-time optimized versions of their most time-critical Hamsterspeak scripts. <br />
<br />
Henceforth is a stack-based language, designed to obviate register allocation problems and minimize script size. You should read James's [[How_does_plotscripting_work%3F#The_Stack | excellent analogy of a stack]], if you aren't familiar with the concept. Hamsterspeak uses the stack for ordering scripts; Henceforth, however, uses the stack for storing data.<br />
<br />
<br />
==A Henceforth Primer==<br />
<br />
Although Henceforth is not a compiled language, you can pretend that "source" Henceforth is compiled into "binary" HFZ lumps, which the game engine runs, and only [[User:Sorlok_reaves | one person]] will cringe and correct you. So let's have a look at some Henceforth source code, shall we? Any text after a [http://dictionary.reference.com/browse/hash%20mark hash mark] until the next newline is considered a comment, and [http://dictionary.reference.com/browse/whitespace whitespace] is used to separate commands. Consider the following script.<br />
#One way to separate commands is by hitting "Enter" after each command.<br />
4 #Push 4<br />
5 #Push 5<br />
add #Pop the top two elements and add them. Push the result.<br />
@result #Pop the top element and store it in the variable "result".<br />
<br />
#Another way to separate commands is using spaces.<br />
4 5 add @result<br />
Most programmers will use a mix of spaces and newlines to keep their scripts clear and sensible. For example, "4 5 add" should probably always be on the same line. Regardless of what format you use, manipulation of the stack is the same. The following diagram shows how the stack (and the variables list) changes as the HVM reads each command, from left to right.<br />
<br />
[[Image:Fmf_stack_diagram.png]]<br />
<br />
<br />
==The "Pure" Virtual Machine==<br />
<br />
From a theoretical point of view, Henceforth does not require a location to store variables. Rather, we could simply add '''block labels''' and a delayed execution ('''prepending''') command:<br />
* Surrounding some source with '''\name{}''' will label that block with "name"; if '''name''' later appears as a command, this block will be inserted in-place. Later writing '''/name''' will un-define "name". If multiple blocks are labeled similarly, the most-recent label is considered valid. <br />
* Pre-pending "&" to a command will push the result of that command ''into the source code itself'', at the time that command is first encountered. <br />
<br />
'''Block labels''' allow functions to be written very naturally:<br />
#The "modulo" function expects two arguments to be<br />
# on the stack; it transforms (X Y) to (X%Y)<br />
\modulo {<br />
swap dup rot dup rot swap #Now we have (X Y X Y)<br />
div mult sub #The heart of our algorithm<br />
}<br />
There's no need to "return" anything; the labeled block operates in a way that leaves one item on the stack. Here's some code to use this block as a function:<br />
#We want to compute 7%3<br />
7 3 modulo #After this, "1" is left on the stack<br />
<br />
This is, in fact, the exact syntax for writing a function in Henceforth. Now, let's cover '''prepending''':<br />
#Avoiding variables with named blocks.<br />
4 5 add <br />
\result{ #Label the start of the "result" block<br />
&pop #This is tricky: the first time we encounter this<br />
# (i.e., when "\result" is defined), it executes "pop",<br />
# resulting in "9", which is pushed into the source.<br />
} #Closes the "\result" label.<br />
<br />
result #Calling "result" will now push "9".<br />
<br />
Unlike named blocks, prepending is not actually implemented in Henceforth; you can ignore that last bit of code if you don't understand it. Which brings us to our main point:<br />
<br />
<big><big style="color:green;"><br />
'''Henceforth was designed around a "pure" VM'''</big><big>, but it was then modified to </big><big style="color:green;">'''run efficiently'''</big><big> on a phone, and to </big><big style="color:green;">'''allow an easy mapping'''</big><big> from existing Hamsterspeak scripts. <br />
</big></big><br />
<br />
It is crucial that you keep the ideal virtual machine in mind, or else you will start asking about "Henceforth's registers" (which don't exist) or you'll start using variables way too much. You also need to be cognizant of the dirty details required to kludge Hamsterspeak onto a phone; otherwise, things like script-level subroutines and our extravagant caching mechanism will seem redundant and senseless.<br />
<br />
(Note to readers: At this point, if you're only interested in the Henceforth language, you can skip down to the section on [[Henceforth_VM#Henceforth Syntax Dictionary | Henceforth Syntax]].)<br />
<br />
<br />
==Where the HVM Fits Into the Game Loop==<br />
<br />
Let's pretend that on a certain map in your game, a certain NPC is set up to "run script 4" when the user walks up to it and presses "Accept". Upon pressing "Accept", the game engine checks if this NPC has a script ID assigned to its "accept" behavior. The classic OHR performs this same task. If one exists, it is initialized and cached. At this point, the game engine continues with its tasks for that cycle (e.g., message boxes, vehicles). This (including user input) constitutes the "Update" phase of the game loop. Following this is the "Paint" phase, which shows one frame's worth of updates to the user. After painting, the tick count is incremented, and the loop repeats. The first thing that happens then is the "scripts" phase, during which scripts are actually executed (see below). The loop repeats indefinitely.<br />
<br />
[[Image:Hf_flowchart.png]]<br />
<br />
<br />
<br />
===Some Technical Considerations===<br />
<br />
When a script is triggered by any means, it suspends the currently-executing script; see "The Stack" in "[[How does plotscripting work?]]". This makes sense; if your script calls another script, one expects the new script to take over. It also means that if you step on several map tiles which each have (long) scripts, the last maptile's script will finish first. (Threading will probably change this.)<br />
<br />
But wait, there's more! Several small but important details of script initialization include:<br />
# If you trigger the same script twice (e.g., by stepping on two copies of an NPC one after the other) the script loader will detect that you are trying to load a script with the '''same id''' as the currently executing script. If the "Permit double-triggering of scripts" general bitset is on (or if the script is explicitly called by ''another script'') then this is allowed, and a second instance of the script is initialized. <br />
# When a script instance is '''first''' loaded, its "delay" counter is set to 2 ticks. This is ''not'' the same thing as instructing the script to "wait", because it remains the active script while delaying. If another script pre-empts this one, the delay counter does not decrement until the new script finishes. (When the new script finishes, the old one immediately resumes what it was doing, be it running or decrementing the "delay" counter.) <br />
# To prepare for parallel scripts, the active script is pushed to the top of the "waiting" stack, and is manipulated by a reference ID. This way, all scripts are kept in one place, and a future, parallel version of GAME_FMF can just scan through this list once each cycle, updating all scripts until they enter a waiting state.<br />
<br />
<br />
<br />
<br />
=Virtual Hardware Specification=<br />
<br />
The [http://en.wikipedia.org/wiki/Virtual_machine Wikipedia page] for Virtual Machines focuses on their use in emulating existing hardware. In fact, there is no hardware version of the HVM, just like there is no hardware equivalent of the Java Virtual Machine. Rather, Henceforth defines an unambiguous virtual hardware specification which can be ported to any platform with guaranteed interoperability. Currently, the only platform with a working HVM is the OHRRPGCEFMF, due to the bias of the HVM's design towards mobile devices.<br />
<br />
Anything in the world can be described by its nouns and verbs --the things it '''has''' and what they '''do'''. So let's inspect the '''pieces''' of the HVM and how they '''interact'''.<br />
<br />
<br />
==VM Data Structures==<br />
<br />
[[Image:Fmf_hvm_data_structures.png]]<br />
<br />
Green and red lines in this diagram indicate references from one item to another, although they are not drawn for every single reference (because that would be craaaaaaazy). <br />
<br />
The Script UDT block is particularly garish, because it is accommodating two different script types: user-defined scripts and named subroutines. Each component is colored differently depending on its use.<br />
<br />
<br />
=== What these do, top-down ===<br />
<br />
One way of learning how these data structures work is by discussing what each piece does on its own, and then assuming their combination. This is like studying one of those [http://www.applesource.com/peeler.html apple machines]; we say that this piece pierces the apple and holds it, that piece slices the skin off, and that blade cores the apple.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_top-down.png|link=FMF:The_HVM,_From_the_Top_Down]] || valign="top" | '''[[FMF:The_HVM,_From_the_Top_Down | Top-Down Description of the HVM]]'''<br>Read on for a detailed summary of the HVM's parts.<br />
|}<br />
<br />
<br />
<br />
===What These Do: Bottom-Up Description===<br />
<br />
If you've ever seen an [http://www.applesource.com/peeler.html apple machine] like the one just mentioned, you're probably aware that describing each element does absolutely nothing to help you understand how the thing works --you have to actually use it on an apple (preferably very slowly) to see how the magic is done. This is the essence of a bottom-up description: describe how certain parts '''interact''' at critical moments in the operation of the whole.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_bottom-up.png|link=FMF:The_HVM,_From_the_Bottom_Up]] || valign="top" | '''[[FMF:The_HVM,_From_the_Bottom_Up | Bottom-Up Description of the HVM]]'''<br>Read on for a detailed summary of how the HVM's parts interact.<br />
|}<br />
<br />
<br />
<br />
<br />
=HF Source Code & Formats=<br />
<br />
Strictly speaking, Henceforth has three different formats for its source code, and there is no such thing as "compiled Henceforth". These three formats are '''Format T''' (text-based), '''Format B''' (bytecode), and '''Format HF''' (compressed). Practically, most user code is generated in '''Format T''', which is then converted to '''Format B''' and compressed (to '''Format HF''') for storage. The HVM reads all three formats, but in doing so it converts T & HF to B. <br />
<br />
That said, we shall begin our study of Henceforth syntax by exhaustively reviewing all Henceforth commands in "source code" format ('''Format T'''), and then describe the mappings to other formats. A few commands cannot be explicitly represented in text format, as the following table details. We shall provide a recommended way of typing these when possible.<br />
<br />
{| cellspacing=0<br />
| &nbsp; || style="border: 1px solid black;" | '''Type''' || style="border: 1px solid black;" | '''Use'''<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format T || valign="top" style="border: 1px solid black;" | text || valign="top" style="border: 1px solid black;" | All human interaction (coding, discussing ideas, etc.) occurs in this format. Thins like script-local subroutines and function parameters cannot be expressed explicitly in this format.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format B || valign="top" style="border: 1px solid black;" | bytecode (binary) || valign="top" style="border: 1px solid black;" | The HVM executes Format B code. The HSP2HF cross-compiler outputs this format. Contains Henceforth Extensions for Scripting.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format HF || valign="top" style="border: 1px solid black;" | compressed || valign="top" style="border: 1px solid black;" | The XRPG specification requires all Format B code to be compressed into format HF lumps, which are then stored locally on the user's phone.<br />
|}<br />
<br />
Henceforth source is a rather slim language. Numbers and strings are supported, but concatenations like "Hello"+" world" and decimals like 2.4 are not. A syntax exists for "do" and "if" statements, and for declaring functions and variables. Several smaller hacks exist for efficiency's sake, as described earlier. And that's it. Anything else is either a "primitive" like '''add''', or a "block label", subject to the polymorphic lookup that characterizes Forth-like languages. Henceforth is not based explicitly on Classic Forth, but it hopes to learn from the mistakes of this peculiar family of languages. One of the main disadvantages to ANSI Forth is its pandering to ANSI C fixations, overcomplicating a language which, at its heart, always championed simplicity and elegance.<br />
<br />
<br />
==Format B Bytecode Specification (HFB)==<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_format-b.png|link=FMF:HFB]] || valign="top" | '''[[FMF:HFB | Format B Specification]]'''<br>Format B code is detailed into exhaustion on a separate page.<br />
|}<br />
<br />
<br />
<br />
<br />
==Henceforth Syntax Dictionary==<br />
<br />
This section contains all of the commands that you could enter into a run-time interpreter (if such a one existed). There is no reference to HSpeak scripts, GAME API calls, or anything else; this section simply explains Henceforth as a language.<br />
<br />
Think of it this way: A Henceforth "script" (called a '''subroutine''') is a slimmed-down version of a Henceforth HSpeak "script" (called a '''script'''). A script maintains a separate stack, for one thing, and script-local subroutines, for another. So, by way of example, calling "wait" within a subroutine would cause the HVM to crash. A script cannot be specified in textual format -that's only a recommended part of the specification, not required. A global script called "root" is used to load subroutines which would otherwise have no parent script.<br />
<br />
<br />
===Numbers===<br />
<br />
Numbers are represented by signed integers such as 2, 18, -35, or 43. When the HVM encounters a number, it pushes it to the stack. That's all for numbers, so let's cover stack effect diagrams briefly; they're necessary to understand more complicated features. Consider:<br />
{| border="1" cellpadding="3" rules="all"<br />
|-<br />
| <tt style="font-size:10pt">1 2 3</tt>|| <tt style="font-size:10pt">7</tt> || <tt style="font-size:10pt">1 2 3 7</tt><br />
|}<br />
<br />
(Note that stack-effect diagrams are oriented with the top of the stack on the right.)<br />
<br />
This diagram implies that the stack starts with the elements 1, 2, and 3 already in it, is then given the command "7", and the resulting stack is now 1, 2, 3, then 7. These is an example of the ''prototype'' style of learning; to teach you how to use a command, I give you a real-world example of how it works. <br />
<br />
If I want to write a stack effect diagram without the fancy table, I might use parentheses and the "--" symbol to mean "the operator we're talking about". For example: "if the interpreter encounters -5, the resulting stack-effect diagram would be <tt style="font-size:10pt">(1 2 3 -- 1 2 3 -5)</tt>". <br />
<br />
For convenience, hexadecimal numbers are allowed, with a '''0x''' prefix. Internally, however, they are simply stored as decimals.<br />
<br />
<br />
===Primitives===<br />
<br />
When the HVM interprets a literal string like '''exp''', '''mycode''', or '''abs''', it searches for the relevant subroutine. The primitives are exceptions to this; they are always executed immediately. A primitive consists of lowercase letters and underscores, and must contain at least one letter. We call this combination its '''symbol name'''.<br />
<br />
Here is a list of primitives, and their stack-effect diagrams:<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Primitive''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="5"| Stack Manipulation || <tt style="font-size:10pt">dup</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 3 )</tt> || "Duplicate" the top of the stack.<br />
|-<br />
| <tt style="font-size:10pt">swap</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 3 2 )</tt> || "Swap" the top two elements of the stack.<br />
|-<br />
| <tt style="font-size:10pt">drop</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 )</tt> || "Drop" the top element off the stack.<br />
|-<br />
| <tt style="font-size:10pt">over</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 2 )</tt> || Pull the penultimate element "over" the top element.<br />
|-<br />
| <tt style="font-size:10pt">rot</tt> || <tt style="font-size:10pt">( 1 2 3 4 -- 1 4 3 2 )</tt> || "Rotate" the top three elements of the stack.<br />
|-<br />
| rowspan="5"| Arithmetic || <tt style="font-size:10pt">add</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 5 )</tt> || Addition<br />
|-<br />
| <tt style="font-size:10pt">sub</tt> || <tt style="font-size:10pt">( 7 3 2 -- 7 1 )</tt> || Subtraction<br />
|-<br />
| <tt style="font-size:10pt">mult</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 6 )</tt> || Multiplication<br />
|-<br />
| <tt style="font-size:10pt">div</tt> || <tt style="font-size:10pt">( 1 7 3 -- 1 2 )</tt> || Division<br />
|-<br />
| <tt style="font-size:10pt">random</tt> || <tt style="font-size:10pt">( 1 4 8 -- 1 6 )</tt> || Push a random number to the stack.<br />
|-<br />
| rowspan="4"| Bitwise Operators || <tt style="font-size:10pt">b_xor</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 17 )</tt> || Exclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_or</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 53 )</tt> || Inclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_and</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 36 )</tt> || Bitwise "And"<br />
|-<br />
| <tt style="font-size:10pt">b_not</tt> || <tt style="font-size:10pt">( 1 52 -- 1 2147483595**)</tt> || Bitwise "Not"<br />
|-<br />
| rowspan="6"| Logical Operators || <tt style="font-size:10pt">eq</tt> || <tt style="font-size:10pt">( 1 2 2 -- 1 1 )</tt> || Logical "Equals"<br />
|-<br />
| <tt style="font-size:10pt">lt</tt> || <tt style="font-size:10pt">( 1 5 2 -- 1 0 )</tt> || Logical "Less Than"<br />
|-<br />
| <tt style="font-size:10pt">not</tt> || <tt style="font-size:10pt">( 1 5 1 -- 1 5 0 )</tt> || Logical "Not"<br />
|-<br />
| <tt style="font-size:10pt">and</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 1 )</tt> || Logical "And"<br />
|-<br />
| <tt style="font-size:10pt">or</tt> || <tt style="font-size:10pt">( 1 5 0 1 -- 1 5 1 )</tt> || Inclusive Logical "Or"<br />
|-<br />
| <tt style="font-size:10pt">xor</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 0 )</tt> || Exclusive Logical "Or"<br />
|}<br />
**Please note: For now, the HVM doesn't use two's compliment to store integers. Hence, ~52 is masked with INT_MAX, which is 0x7FFFFFFF in 32-bit notation. We will probably change this for version 2.0, since people who use b_not (e.g., hackers) will be expecting the opposite behavior.<br />
<br />
<br />
There is no boolean type; the number zero is considered "false", and any other number is considered "true". For clarity, the number "1" is the preferred way to represent "true".<br />
<br />
The primitives "and" and "or" do ''not'' short-circuit. This is because a short-circuiting operator generates an ambiguous stack-effect diagram, and achieves no gain in performance or security (since it operates on values which have already been computed.) The [[HSP2HF | Cross-Compiler]] has an example of how to perform short-circuiting calculations in a manual way; see the section on Mathematical Functions.<br />
<br />
A number of additional keywords such as '''break''' and '''continue''' will be discussed in the section on If statements and Loops.<br />
<br />
===Blocks (Subroutines)===<br />
<br />
Labeled blocks, also called subroutines, are the core of Henceforth's modularization. They are strikingly similar to primitives, except that they can be defined, undefined, and overloaded. Labels, like primitives, are identified by a '''symbol name'''.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This creates a function called "mod", which performs a series of stack manipulations and basic arithmetic.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;7 3 mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Traditionally, we push the "parameters" to the subroutine in increasing order, and expect it to remove these arguments (and replace them with a return value, if any) during the course of its operation.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Overriding a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;drop drop 0<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;After executing this snippet, "mod" will have two definitions. Calling "mod" will result in the latter being evoked, which simply clears the parameters and returns zero.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Un-defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">/NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;/mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;If we execute the code from "Overriding a Subroutine", and then execute this code, "mod" will be left with a single definition: the first one.<br />
|}<br />
<br />
<br />
Any labeled block remains in effect even if the script which defined it finishes execution; make sure to un-define any subroutines you do not wish to linger. The HVM capitalizes on this behavior to limit the number of built-in primitives. The modulus operator described earlier is an example, as is "less than or equal to", which can be defined compositely. Here is a list of all the subroutines the HVM loads by default, with their stack-effect diagrams.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Subroutine''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="2"| Arithmetic || <tt style="font-size:10pt">exp</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 8 )</tt> || Compute x raised to the y ("exponent")<br />
|-<br />
| <tt style="font-size:10pt">mod</tt> || <tt style="font-size:10pt">( 1 10 4 -- 1 2 )</tt> || Calculate the "modulus"<br />
|-<br />
| rowspan="4"| Logical Operators || <tt style="font-size:10pt">neq</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 1 )</tt> || Logical "Not Equal To"<br />
|-<br />
| <tt style="font-size:10pt">lte</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 1 )</tt> || Logical "Less Than or Equal To"<br />
|-<br />
| <tt style="font-size:10pt">gt</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 0 )</tt> || Logical "Greater Than"<br />
|-<br />
| <tt style="font-size:10pt">gte</tt> || <tt style="font-size:10pt">( 1 63 45 -- 1 1 )</tt> || Logical "Greater Than or Equal To"<br />
|-<br />
| rowspan="2"| Convenience Methods || <tt style="font-size:10pt">true</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 45 52 1 )</tt> || Locus-of-control for "true"<br />
|-<br />
| <tt style="font-size:10pt">false</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 45 45 0 )</tt> || Locus-of-control for "false"<br />
|}<br />
<br />
<br />
===Variables===<br />
<br />
Shuffling the stack beyond the fourth element can get messy. As such, subroutines can allocate ''local variables'' to the scope of the parent '''script''' they are running in. Note that variables follow a similar naming convention to subroutines. They are accessed using the @VAR and VAR@ commands. You can remember them easily by putting an imaginary "stack" next to them: <br />
* '''(stack)@VAR''' takes a value from the stack and throws it '''at''' VAR. This is the same as setting VAR = pop().<br />
* '''VAR@(stack)''' takes the value in VAR and throws it '''at''' the stack. This is the same as pushing VAR.<br />
<br />
Here is some sample source for the '''lte''' function, written using variables. Note that, in reality, '''lte''' only uses the stack. This is much more efficient for the HVM, which only initializes the local variable store if it detects your script trying to access it.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using Variables'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\lte{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Pop the stack twice to get our local variables.<br>&nbsp;&nbsp;&nbsp;&nbsp;#We assume that we are testing @lhs <= @rhs<br>&nbsp;&nbsp;&nbsp;&nbsp;@rhs&nbsp;&nbsp;@lhs<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the first test (<) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res1<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;lt&nbsp;&nbsp;@res1<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the second test (==) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res2<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;eq&nbsp;&nbsp;@res2<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our two tests and do an or test. Leave it on<br>&nbsp;&nbsp;&nbsp;&nbsp;# the stack to be returned<br>&nbsp;&nbsp;&nbsp;&nbsp;res1@&nbsp;&nbsp;res2@&nbsp;&nbsp;or<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;As an exercise, you should try to convert this to a stack-based alternative. It should end up more concise, and also faster. (Profile it, to be sure).<br />
|}<br />
<br />
<br />
===Do/If & Loops===<br />
<br />
Most Forth-like languages have an "if" statement, and Henceforth is no exception. However, rather than encapsulating loops into labeled blocks (like Classic Forth), it adds another keyword: "do". This serves two purposes: first, it allows for better inlining than subroutines, and second, it provides a locus of control for jump commands. The second rationale is sometimes written as "otherwise, we'd have to use '''goto'''".<br />
<br />
Inside of a do loop, the commands '''break''' and '''continue''' also take effect; you should read [[HSZ#Id_11:_break | the HSZ documentation]] for an example of how these work conceptually. The mechanism for '''break''' and '''continue''' is different in the HVM, but the effect is the same.<br />
<br />
The standard Hamsterspeak engine also allows breaking out of multiple loops at once. In order to keep the syntax clean in Henceforth, we introduce two bytecodes for that purpose: '''break_x''' and '''continue_x'''. These take the element from the top of the stack, and break out of that many loops. The '''break''' command is effectively equivalent to "1 break_x". <br />
<br />
Thus, we add five more keywords to the list of Henceforth primitives: '''do''', '''if''', '''else''', '''break''', and '''continue'''. Brackets are used to denote code blocks where relevant. We will cover the syntax in simple examples. The first is a user-defined subroutine, "divide_safe", which avoids dividing by zero, and the second is an example of the Fibonacci sequence using "do" to achieve the effect of a "while" loop.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Save Divide-by-Zero Handling'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\divide_safe{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Do a zero-check on the divisor<br>&nbsp;&nbsp;&nbsp;&nbsp;dup&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;#Now, branch on that<br>&nbsp;&nbsp;&nbsp;&nbsp;if{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pop&nbsp;&nbsp;pop&nbsp;&nbsp;0<br>&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;div<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Note that returning zero is just one way of handling the erroneous case. Also, take note of the two "pop" commands; these are required!<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Fibonacci Numbers'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\fibonacci{<br>&nbsp;&nbsp;&nbsp;&nbsp;@count&nbsp;&nbsp;&nbsp;&nbsp; #Must be >=1<br>&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;@fprev&nbsp;&nbsp;#fib(n-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;@fnow&nbsp;&nbsp; #fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Test-and-decrement<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;-1&nbsp;&nbsp;add&nbsp;&nbsp;@count<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Iterate & Accumulate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprev@&nbsp;&nbsp;fnow@&nbsp;&nbsp; add<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fnow@&nbsp;&nbsp; @fprev<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fnow<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Re-do the loop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fnow@<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This subroutine fails given zero as input. Modify it by adding a simple if statement before the main loop. <br />
|}<br />
<br />
This second example is far more interesting, since it answers the question "How do I Loop in Henceforth?"<br />
<br />
'''''A Note About Recursion''''': Although Henceforth fully supports recursion, the HVM is not designed for heavily-recursive functions. When possible, please use an iterative solution.<br />
<br />
<br />
<br />
==Henceforth Extensions for Scripting==<br />
<br />
The Henceforth specification recommends (read: ''does not require'') that the following pseudo-syntax be adopted to describe the commands which appear only in the '''Format B''' code for HSpeak script equivalents. A vanilla HVM should recognize these tokens and either ignore them, simulate them, or generate an informative error message when it encounters them.<br />
<br />
Backwards compatibility need not be preserved when converting between the '''Format B''' and '''Format T''' equivalents for script extensions.<br />
<br />
<br />
<br />
===HSpeak API Calls & User Scripts===<br />
<br />
The HVM has knowledge of most "built-in" functions that Hamsterspeak provides. These are called like any other subroutine, with one exception: most format converters create specialized subroutine calls out of them. In other words, if I write a call to '''wait_npc''' in my ('''Format T''') code, the converter will create ('''Format B''') source that points ''explicitly'' to the definition of '''wait_npc''' in the HVM; it is '''''not''''' possible to overload '''wait_npc'''. This breaks our promise of fully-interchangeable source formats, but it is hardly noticeable to most users, and keeps our global function lookup table from becoming too cluttered.<br />
<br />
See [[FMF:Plotdict]] for a listing of all API calls and their syntax in Henceforth.<br />
<br />
User-defined scripts are treated exactly like API calls; they are assigned IDs and called explicitly. <br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Calling an HSpeak API Method'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;12<br>&nbsp;&nbsp;show_txt</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Shows text box with ID 12. The api call '''show_txt''' is the shorthand for "show text box", [[FMF:Plotdict | as listed here]]. <br />
|}<br />
<br />
<br />
===Strings===<br />
<br />
Strings in Classic Forth are handled by simply pushing a series of integers (for each letter) to the stack. Eventually, Henceforth may go this route, but for now the API calls to strings are so simple that we chose a much simpler implementation:<br />
* Strings are a series of letters between double-quotes, like "Hello World", "Going?", or "Unicode: \u1000". As hinted by the last example, the HVM supports Unicode.<br />
* When a string is encountered (usually at the top of a script) it is pushed to the '''String Table''' for that script. Strings are pushed in order, cannot be removed or easily manipulated, and have IDs in increasing order.<br />
<br />
This is an ''ad hoc'' implementation of strings, until we get a clearer picture where the standard OHR's development is going with these ideas.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using a String'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;"Hello world!"<br>&nbsp;&nbsp;0 show_str</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Defining "Hello world!" will place this string at index zero; pushing 0 and then calling the API function '''show_str''' will reference that string explicitly.<br />
|}<br />
<br />
<br />
===Script-Local Subroutines===<br />
<br />
Here's where Henceforth starts to really get picky. When a Hamsterspeak script is converted to Henceforth, it is basically converted into a series of labeled blocks that call each other. The keen observer will note that representing these blocks as subroutines is inefficient, since they can be consistently enumerated. <br />
<br />
With that in mind, subroutines are allowed to define "script-local" subroutines which operate along a single thread of control within a named block. These are defined with a given ID, and called with an array-like syntax. They cannot be un-defined or overloaded, although theoretically they could be replaced (by re-defining them).<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\[ID]{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\[5]{<br>&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;plus<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Our "plus two" example from before, assigned to index 5.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">[ID]()</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;16&nbsp;&nbsp;[5]()</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This pushes 16, and calls local block 5. The result is a stack containing the number 18, if we define [5] as listed above. ''The parentheses, although not technically necessary, do a great deal to visually distinguish script-local subroutines from function parameters.''<br />
|}<br />
<br />
<br />
===Local Parameters & Return Values===<br />
<br />
'''''MAKE A NOTE: Give an example of how my_script{ exp } calls "exp" ON the stack of my_script. '''''<br />
<br />
An astute reader will have noticed that scripts currently have no way of passing values to other scripts. Calling "5 6 my_func" won't work, because '''my_func''' will get its own independent stack. This problem is circumvented through the use of local parameters, which also provide a slight speedup for small, frequently called subroutines. The local parameters are accessed by ID, with -1 a special ID that refers to the script's return value. <br />
* Executing @[5] will "pop the (parent) stack to local parameter 5"<br />
* Executing [5]@ will "push local parameter 5 onto the (current) stack<br />
For the return value, it's backwards:<br />
* Executing @[-1] will "pop the (current) stack to the return value"<br />
* Executing [-1]@ will "push the return value onto the (parent) stack", '''''unless''''' the return value is set to ''invalid'' (INT_MIN, for now).<br />
The only point of interest is that last bit. The problem is, the HSpeak command "return" sets the return value, and "exitscript" will exit and return that value. Some scripts simply don't set a return value, so the HVM pushes the '''most recently computed integer''' if it detects an ''invalid'' return value.<br />
<br />
===Comments===<br />
<br />
Henceforth supports comments, although it's important to remember that Formats B and HF will drop your comments; they can't be encoded. A comment begins with a hash mark (#) and ends with a line feed. <br />
<br />
<br />
===Global Variables===<br />
<br />
All that's left are global variables, which are manipulated with syntax like '''@[65.G]''' and '''[65.G]@'''. We realize this is a bit awkward, and we're open to a cleaner syntax (once we actually get a release going).<br />
<br />
<br />
<br />
= The Cross-Compiler =<br />
<br />
The OHRRPGCE FMF provides a tool to help convert existing Hamster Speak scripts to Henceforth lumps; read about it here:<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_hsp2hf.png|link=HSP2HF]] || valign="top" | '''[[HSP2HF | The HSP2HF Cross-Compiler]]'''<br>Read on for details about this tool.<br />
|}<br />
<br />
<br />
=Final Considerations=<br />
<br />
Henceforth is a big step away from Hamsterspeak, and it is best to approach it with an open mind. Try solving [http://projecteuler.net/index.php?section=problems common problems] with snippets of Henceforth; even if you simply write the solution on paper it will help you get used to thinking in a stack-based fashion. <br />
<br />
The developers of Henceforth are always interested in advancing the language; feel free to post a message [[User_talk:Sorlok_reaves | on Seth's page]] with your ideas --except, try to avoid buzzwords. Henceforth will never have objects, threading (which is ''different'' from multi-tasking), or static typing; it's just not that kinda language.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=FMF:The_HVM,_From_the_Bottom_Up&diff=22307
FMF:The HVM, From the Bottom Up
2009-02-03T08:45:21Z
<p>Sorlok reaves: /* Whoops, switched pop and push. */</p>
<hr />
<div>{{FMF}}<br />
<br />
<br />
This page describes the workings of the HVM in relation to the bytecodes it processes. For your reference, here is a copy of the data structures diagram:<br />
<br />
<br />
[[File:Fmf hvm data structures.png]]<br />
<br />
<br />
We will now list some critical operations performed by the HVM and how it uses the data structures to track these operations. Before reading this section, you should probably read [[Henceforth_VM#Henceforth Syntax Dictionary | the syntax dictionary]], so that you have an idea of Henceforth's intended functionality. Otherwise, this section might not make a lot of sense.<br />
<br />
For these charts, the green background is default. If the background is red, that means the chart only applies to a full-blown user script. A subroutine will fail, since it doesn't have the requisite data structures allocated. We check if a subroutine is being run by seeing if the PC Stack's size is greater than 1. Checking another stack's size to determine this is non-standard. <br />
<br />
For brevity, when we say "advance the PC by one" (or, equivalently, PC += 1), we are referring to a "smart increment". For multi-word bytecodes, this means we have to increment by the length of the bytecode, not simply by 1.<br />
<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#C8FFC8" colspan=2 |'''''Lifecycle'''''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
After the HVM initializes, and before it starts running scripts.<br />
| valign="top" | '''Response'''<br />
* Create a new entry in the script cache<br />
** New Name = "root"<br />
** ID = -1<br />
** Pinned = true<br />
** In Use = 1<br />
** Bytecode = new Array[]<br />
** DoTable = new Array()<br />
** Optionally, load into "Bytecode" the contents of "global.HF", or read directly from user input.<br />
** '''Note:''' We do not create an entry in the script name multiplexer; root is effectively read-only.<br />
* Make a new Script UDT:<br />
** Local Stack = new Stack()<br />
** Program Counter Stack = new Stack(0)<br />
** Script Cache Pointer Stack = new Stack(Script Cache Reference)<br />
** Do/If Stack Stack = new Stack(new Stack())<br />
** Local Variable Hash Stack = new Stack(new Hash())<br />
** ID = -1<br />
** Parent Script Pointer = -1<br />
** Local Variable Array = new Array()<br />
** String Table = new Array()<br />
** Local Function Start Indices = new Array()<br />
** Return Address Stack = new Stack()<br />
** '''Note:''' If the root script attempts to use script-only data structures (e.g., local parameters), the behavior of the HVM is undefined.<br />
* Make a new entry in the Running Scripts table:<br />
** Script = our new Script UDT<br />
** Wait = false<br />
** Wait On = 0<br />
* Insert this new Running Scripts entry at index zero (or as the head node, if it's a linked list). This spot is reserved for the root script.<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all"<br />
|bgcolor="#C8FFC8" colspan=2 |'''''Functional Primitives'''''<br />
|-<br />
| '''Trigger'''<br />
dup<br />
<br>swap<br />
<br>drop<br />
<br>over<br />
<br>rot<br />
<br>add<br />
<br>sub<br />
<br>mult<br />
<br>div<br />
<br>random<br />
<br>b_xor<br />
<br>b_and<br />
<br>eq<br />
<br>lt<br />
<br>not<br />
<br>and<br />
<br>xor<br />
<br>b_not<br />
<br>b_or<br />
<br>or<br />
| valign="top" |'''Response'''<br />
* Pop a pre-defined number of arguments from the local stack.<br />
* Perform the desired math/logic<br />
* Push the resultant, if applicable, to the local stack.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#C8FFC8" colspan=2 |'''''Numbers'''''<br />
|-<br />
| '''Trigger'''<br />
The HVM encounters a "short integer" or a "full-width integer"<br />
| '''Response'''<br />
Push that number to the "local stack" of the currently-running script.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#FF6D6D" colspan=2 |'''''Script-Local Subroutines'''''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "define local script" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* In the "local function start indices" array, at index '''x''', insert (PC+1)<br />
* Advance the PC without executing commands until you reach an "end define".<br />
* Advance the PC by one.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "call local script" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* Push (PC+1) to the "return address stack"<br />
* Set the PC to the "local function start indices" array value at index '''x'''.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters an "end define" and the "return address stack" is not empty.<br />
| valign="top" | '''Response'''<br />
* Pop the "return address stack" and set the PC to this value.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#FF6D6D" colspan=2 |'''''Global Variables'''''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "retrieve global variable" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* If '''x''' is equal to the magic number (0x3FF for 10-bit ids), then pop the stack and store that value in '''x'''.<br />
* Push the value of that variable from the Global Variable Table.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "set global variable" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* Pop the stack.<br />
* If '''x''' is equal to the magic number (0x3FF for 10-bit ids), then pop the stack and store that value in '''x'''.<br />
* Store the popped value at location '''x''' in the Global Variable Table.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#FFFFFF" colspan=2 |'''''Call Subroutines'''''<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM, while running a script with id '''y''' in the Running Scripts Table, encounters a "call user-defined function by id" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* Retrieve a new entry in the script cache<br />
** Get entry '''x''' from the script lookup table; if "Cache" == -1, then load the source from the appropriate "Chunk" and add a new entry to the script_cache:<br />
*** New Name = null<br />
*** ID = '''x'''<br />
*** Pinned = false<br />
*** In Use = 0<br />
*** Bytecode = loadFromChunk()<br />
*** Do Table = new Array[do_udt]<br />
** For the appropriate script cache entry, set InUse = InUse + 1<br />
* Make a new Script UDT:<br />
** Local Stack = new Stack[]<br />
** Program Counter Stack = new Stack(0)<br />
** Script Cache Pointer = new Stack(id of the script cache entry we retrieved)<br />
** Do/If Stack Stack = new Stack(new Stack())<br />
** Local Variable Hash Stack = new Stack(new Hash{})<br />
** '''''*Note:''''' The HVM can leave "optional" components like this un-initialized until they are actually used.<br />
** ID = x<br />
** Parent Script Pointer = y<br />
** Local Variable Array = new Array[max+1]<br />
** String Table = new Array[]'''''*'''''<br />
** Local Function Start Indices = new Array[]'''''*'''''<br />
** Return Address Stack = new Stack[]<br />
** Set Local Variable Array to -1 at index 0<br />
* Make a new entry in the Running Scripts table:<br />
** Script = our new Script UDT<br />
** Wait = false<br />
** Wait On = 0<br />
* Insert this new Running Scripts entry one step ahead of script Y; this ensures it will be run next. <br />
* Set script Y's "Wait" flag to "true".<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM, while running a script with id '''y''' in the Running Scripts Table, encounters a "call named subroutine" with name '''exp'''.<br />
| valign="top" | '''Response'''<br />
* Retrieve a new entry in the script cache<br />
** Get entry '''exp''' from the script name multiplexer<br />
** Use '''exp''' to get the appropriate script cache entry; set its InUse = InUse + 1<br />
* In the current script UDT:<br />
** Program Counter Stack.push(0)<br />
** Script Cache Pointer.push(id of the script cache entry we retrieved)<br />
** Do/If Stack Stack.push(new Stack())<br />
** Local Variable Hash Stack.push(new Hash{})<br />
** '''''*Note:''''' The HVM can leave "optional" components like this un-initialized until they are actually used.<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM encounters a "hamsterspeak api call" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* Pop the agreed-upon number of arguments<br />
* Call API function '''x''' with said arguments<br />
* Push the return variable, if applicable<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#FFFFFF" colspan=2 |'''''Local Variables'''''<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM encounters a "push local variable by id" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* Pop the stack<br />
* If '''x''' is equal to the magic number (0x1FF for 9-bit ids), then pop the stack and store that value in '''x'''. Subtract 1 from x.<br />
* Store the popped value in script_udt.local_variable_array[x+1]<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM encounters a "pop local variable by id" with id '''x'''.<br />
| valign="top" | '''Response'''<br />
* If '''x''' is equal to the magic number (0x1FF for 9-bit ids), then pop the stack and store that value in '''x'''. Subtract 1 from x.<br />
* Retrieve the value in script_udt.local_variable_array[x+1]<br />
* Push the retrieved value to the stack<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM encounters a "pop local variable by name" with name '''test'''.<br />
| valign="top" | '''Response'''<br />
* Pop the stack<br />
* Create a key in script_udt.local_variable_hash_stack.top() for "test"<br />
* Store the popped value in script_udt.local_variable_hash_stack.top(){"test"}<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM encounters a "push local variable by name" with name '''test'''.<br />
| valign="top" | '''Response'''<br />
* If the local variable hash stack's top entry has no key at "test", push -1 and return<br />
* Retrieve the value in script_udt.local_variable_hash_stack.top(){"test"}<br />
* Push the retrieved value to the stack<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#C8FFC8" colspan=2 |'''''If Statements'''''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters an "if_start"<br />
| valign="top" | '''Response'''<br />
* Push a new do_udt with startIndex = PC to the do/if stack stack.top()<br />
* Retrieve the script cache entry associated with script_cache_pointer_stack.top(). If this contains a do_udt with the same startIndex, set the elseIndex of this do_udt to that value. Else, set it to -1. Do the same for the endIndex.<br />
* Pop the stack. If this value is not 0, increment the PC. If 0, then set the PC to the elseIndex. (If the elseIndex is -1, scan & dispose bytecodes until the else_start command is found, and then update the script_cache.) Increment the PC.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters an "if_end"<br />
| valign="top" | '''Response'''<br />
* Pop the do/if stack stack.top(). If that entry's endIndex is -1, set endIndex = PC and update the script_cache.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters an "else_start" (This will only happen if the else branch is not taken)<br />
| valign="top" | '''Response'''<br />
* Pop the do/if stack stack.top(). If that entry's elseIndex is -1, set elseIndex = PC and update the script_cache.<br />
* Set the PC = endIndex. (If endIndex is -1, scan & dispose bytecodes until the if_end command is found, and then update the script cache.) Increment the PC<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#C8FFC8" colspan=2 |'''''Loops & Flow Control'''''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "do_start"<br />
| valign="top" | '''Response'''<br />
* Push a new do_udt with startIndex = PC to the do/if stack stack.top()<br />
* If the script_cache entry for this script contains a do_udt with the same startIndex, set the endIndex of this do_udt to that value. Else, set it to -1. Set elseIndex = -2.<br />
* Increment the PC<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "do_end"<br />
| valign="top" | '''Response'''<br />
* Pop the do/if stack stack.top(). If that entry's endIndex is -1, set endIndex = PC and update the script_cache.<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "break" or "break_x"<br />
| valign="top" | '''Response'''<br />
* Set '''x''' = 1 if this is a "break" command, or '''x''' = stack.pop() if this is "break_x"<br />
* while x > 0, do the following:<br />
* Pop the do/if stack stack.top() until you encounter a do_udt with elseIndex == -2. Pop that as well.<br />
* Set PC = endIndex of this do_udt. (If endIndex is -1, scan & discard bytecodes until a do_end is found. Then update the script_cache.) Increment the PC.<br />
* Decrement '''x'''<br />
|-<br />
| valign="top" | '''Trigger'''<br />
The HVM encounters a "continue" or "continue_x"<br />
| valign="top" | '''Response'''<br />
* Set '''x''' = 1 if this is a "continue" command, or '''x''' = stack.pop() if this is "continue_x"<br />
* Pop the do/if stack stack.top() until you have removed '''x''' do_udts with elseIndex == -2. Push the last entry back to the do/if stack stack.top().<br />
* Set PC = do/if_stack_stack.top().top().startIndex + 1<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="4" rules="all" width="70%"<br />
|bgcolor="#FFFFFF" colspan=2 |'''''Subroutines & Strings'''''<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM encounters a "string define" with string '''test string'''.<br />
| valign="top" | '''Response'''<br />
* Push '''test string''' to the end of the string table<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM encounters a "begin define" with string '''my_function'''.<br />
| valign="top" | '''Response'''<br />
* Look for '''my_function''' in the script name multiplexer. If it's not there, add it and set definitions = new Array[int]<br />
* Create a new entry in the script_cache:<br />
** New Name = "my_function"<br />
** ID = -1<br />
** Pinned = true<br />
** In Use = 0<br />
** Do Table = new Array[do_UDT]<br />
* Scan all bytecodes up to and including an end_define into the "Bytecode" field for this new script cache entry.<br />
* Push into script_name_multiplexer{"my_function"} a reference to the script cache entry you just created.<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM encounters an "un-define named subroutine" with string '''my_function'''.<br />
| valign="top" | '''Response'''<br />
* Pop the last definition from the script_name_multiplexer{"my_function"} into variable '''x'''.<br />
* If script_name_multiplexer{"my_function"} has no more definitions, remove it.<br />
* Set script_cache[x] to be a null row; remove it from the array if you can do so without introducing inconsistencies. At the very least, de-allocate all its data.<br />
|-<br />
| valign="top" bgcolor="#FF6D6D" | '''Trigger'''<br />
The HVM encounters an "end_define", or a "return" command in a user-defined script.<br />
| valign="top" | '''Response'''<br />
* If there is a return value in script_udt.local_variable_array[0], push it to the parent script's stack. If not, push the last calculated response ('''$_''')<br />
* Set this script's parent's entry in the running scripts table to Wait = false<br />
* Remove this script from the running scripts table. <br />
* Decrement the in_use field for the script_cache entry for this script_udt<br />
* Delete this script_udt<br />
|-<br />
| valign="top" bgcolor="#C8FFC8" | '''Trigger'''<br />
The HVM encounters an "end_define" in a subroutine.<br />
| valign="top" | '''Response'''<br />
* Pop the script cache pointer stack, set the the corresponding script cache's entry's In Use to In Use -1<br />
* Pop the Program Counter Stack<br />
* Pop the Do/If Stack<br />
* Pop the Local Variable Hash Stack<br />
|}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=User_talk:Bob_the_Hamster&diff=22290
User talk:Bob the Hamster
2009-01-29T04:03:53Z
<p>Sorlok reaves: typo</p>
<hr />
<div>'''Grady''': Hey Bob, i thought this would be an inappropriate question to ask on the HOWTO & FAQ so i decided to ask it here, can i please have some help on how to use MediaWiki or whatever it is on my website, if you don't mind? Thanks :)<br />
<br />
'''Bob''': Sure! I reccomend installing it on a Linux box. Trying to do it on a Windows box is possible, but a huge pain.<br />
# Download the latest Mediawiki 1.4.x from http://wikipedia.sourceforge.net/<br />
# Follow the instructions at http://meta.wikimedia.org/wiki/Help:Installation<br />
# If you have problems, mail me, or ask questions here, but I will answer your questions (if I can) by editing and improving http://meta.wikimedia.org/wiki/Help:Installation<br />
<br />
'''Grady''': Okay, Hmm.. the reccomended ram is a bit high, i only have 128 MB, and alot of that is used by Windows XP :P Oh well i'll give it a shot anyway, dosen't hurt to try, thing is i have to find a decent server that gives free MySQL and PHP and whatever the hell else it needs :P<br />
<br />
'''Mike''': http://swifthost.net - Best. Hosts. Evar.<br />
<br />
----<br />
<br />
'''Bob''': Spam filtering still works just fine in Mediawiki 1.5 ... I just forgot that logged-in users are allowed to say "viagra" without getting filtered.<br />
<br />
----<br />
<br />
'''Mike''': Surely there's a way to get an IP address attached to a username. But, I looked, and couldn't find anything. Or, is that just a 'Bureaucrat' feature?<br />
<br />
'''Bob''': I would have to check the server logs to get that spammer's IP. Bereaucrat just means you can grant Sysop to other users.<br />
<br />
[[User:James Paige|James Paige]] Hey! I really object to the editorial slant of this article! And I especially don't like that bit about encouraging people to bug me about finishing wandering hamster. I have a big enough backlog of e-mail already!<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] Oh, what? Were you just sitting there hitting "reload" on the [[Special:Recentchanges|recent changes]] page, or what? I thought you were busy!?<br />
<br />
[[User:James Paige|James Paige]] Oh, much better. Now you make me sound both lazy '''and''' crass. Seriously, buying me books is not a good way to get me to work on WH. it is a good way to get me to spend hours and hours of my free time reading books, and specificly not working on Wandering Hamster. And it is my lunch break right now. I am aloud to spend my lunch break obsessively reloading and web page I please, aren't I?<br />
<br />
'''The Geek''': I could be really cheeky here and say "what lunch break, oh you mean the one that<br />
+never ends?", but I won't :)<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] The Geek, when you posted that, it was blocked by the spam filter <strike>because James hates you</strike> because you were not logged in, and this page contains the word "viagra". (unfortunately, the spam filter currently uses the full-text of the page, not just the changed parts)<br />
<br />
[[User:Pkmnfrk|Mike C.]] You know you have problems when you have fake arguments with yourself in public places...<br />
<br />
[[User:James Paige|James Paige]] I am perfectly sane. Bob is the one with problems.<br />
<br />
[[User:The Geek|The Geek]]: There, I am a user now. I didn't realize you didn't need to give an<br />
+e-mail address. Thanks for restoring the post. And I agree with Mike :)<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] Actually, the above post was blocked too, because I had not yet added you to the [[not evil]] list. ''Now'' you can post without fear of accadentalluy triggering the filter. I really need to fix the filter so it only filters changes. Any page where someone has ever spoken a blocked word like the v-word is practically locked down. :(<br />
<br />
[[User:The Geek|The Geek]]: Hey, it works most of the time! And thanks again for restoring the post.<br />
<br />
== Wiki issue ==<br />
<br />
[[User:FyreWulff|FyreWulff]] Can you fix the Mediawiki namespace so that we can actually make use of the [[Special:Wantedpages]] ?<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]]: I'm not sure what I need to do to fix it, but I can certainly try.... I guess I just need to create all those missing pages...?<br />
<br />
== Slanderous lies! ==<br />
<br />
[[User:Nintendork|Nintendork]]: Hey, in your user page, it says "I take care of this wiki for my good friend James Paige, who is too busy sculpting naked women to spend any time maintaining his own silly wiki," but I thought James was dead! Are you saying my thought that he was dead for over six years was all a ''lie''?<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]]: He died six years ago, but has spent the last 5 years as a zombie. ''(Handy cooking tip: Strawberry rice-krispy & marshmallow squares make an excellent murder-free brain substitute)''<br />
<br />
[[User:Pkmnfrk|Mike C.]] Hmm, strawberry, eh? I was trying to use peanut butter in my own necromancy project... thanks for the tip!<br />
<br />
== OHRRPGCE Gamelist ==<br />
<br />
[[User:The Geek|The Geek]]: Totally off-topic here, but how did you do the multiple-page bit of the OHRRPGCE gamelist? Did you use a database for it and then call for entries ''x'' to ''x'' or what?<br />
<br />
[[User:Inferior Minion|Inferior Minion]]: It looks like that's exactly what he did. The gamelist page is set to display 8 games and it uses the offset passed through the URL to determine where to start.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]]: My gamelist is written in php, but that particular bit of work is done in my SQL SELECT statement, using a LIMIT clause.<br />
<br />
$query=sprintf('select * from linklist order by %s %s limit %s,%s',$sort,$sortdir,$offset,$limit);<br />
<br />
I have imported the gamelist source code to subversion under svn://gilgamesh.hamsterrepublic.com/ohrrpgce/web/ohrrpgce-static/<br />
<br />
== bobs! ==<br />
<br />
did you get shooted!?<br />
[User:James Paige|James Paige]]: Bob is still in the hospital. He is recovering well from his gunshot wound, but he is still in a coma. He has not spoken, nor given any indication of waking up since the "incident". When somebody has [http://castleparadox.com/ohr/viewtopic.php?t=5253 figured out who did it] I will take the RPG file on a laptop to him and play it in his hospital room, in the hopes that it will wake him up.<br />
<br />
==bugs==<br />
--[[User:Dorumagesu|Dorumagesu]] 16:29, 27 November 2007 (PST) Bob, I lost my bug partially, just I lose sound effects.<br />
<br />
--[[User:Dorumagesu|Dorumagesu]] 17:28, 12 December 2007 (PST) Bob, I can't seem to access bat when the page exists.<br />
<br />
==Uh bob?==<br />
--[[User:Dorumagesu|Dorumagesu]] 17:41, 19 December 2007 (PST) I thought main page was read only. I could edit it...<br />
<br />
--[[User:Dorumagesu|Dorumagesu]] 13:56, 20 December 2007 (PST) The change came up..... don't worry, I erased all the changes I made. That is sooooo weird....<br />
<br />
--[[User:Dorumagesu|Dorumagesu]] 13:02, 26 December 2007 (PST) New topic Bob. Why was the wiki down for the last three days? I couldn't update anything! <br />
<br />
--[[User:Dorumagesu|Dorumagesu]] 10:19, 31 December 2007 (PST) Uh bob, I can't look at the info boxes on game pages. Why?<br />
== Castle Paradox==<br />
<br />
--[[User:Dorumagesu|Dorumagesu]] 08:07, 22 December 2007 (PST) Do you know any reason why Castle Paradox is unavailable? I tried it on 2 computers but it still doesn't work for 2 days.<br />
<br />
--[[User:The Mad Cacti|The Mad Cacti]] 18:12, 22 December 2007 (PST) CP worked for me yesterday, and it works right now too. But it's no use asking James about it, he doesn't run it, [[User:Inferior Minion]] does. See [[Why does Castle Paradox's server go down so often?]]<br />
<br />
==Calehay==<br />
--[[User:Dorumagesu|Dorumagesu]] 18:54, 1 January 2008 (PST) Bob, an annoying person called Calehay is accusing me and being sarcastic about my game, when I am sure it is very nice and it has no mistakes, but bugs in it! He says they aren't bugs! Please help me!<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]]: Help you with what? If you and Calehay have an disagreement about something, that is between you and him. It isn't my place to resolve this sort of dispute. I would suggest talking it over with him via e-mail or [[forums]] or [[IRC]]. The wiki isn't really a chat board.<br />
<br />
--[[User:TwinHamster|TwinHamster]] 16:04, 4 January 2008 (PST) It might be best for you two to carry on whatever dispute you're having through Castle Paradox's private messaging system, so that it's none of this wiki's business.<br />
It might also be a good idea to take advantage of Castle Paradox's journal system and talk about your game there so that you won't have to add fifty updates to your game's wiki page every day; instead, you could post your daily edits on said journal and then update your game's wiki page after big updates are made.<br />
By doing so, you can single-handedly reduce the clutter of the recent changes page, allowing the rest of us wiki-editing folk (Well, me at the least) to easily spot the other changes that have been made.<br />
Thank you.<br />
<br />
==Questions==<br />
--[[User:Dorumagesu|Dorumagesu]] 15:22, 4 January 2008 (PST) Bob, who exactlly is allowed to edit on locked pages?<br />
<br />
--[[User:Dorumagesu|Dorumagesu]] 15:07, 6 January 2008 (PST) Bob, is superwalrusland a page that is related to OHRRPGCE's wiki?<br />
<br />
--[[User:Bob the Hamster|Bob the Hamster]] 15:19, 6 January 2008 (PST) superwalrusland is run by Surlaw, who is a user of the OHR. He is the editor of the [[HamsterSpeak]] online magazine. So superwalrusland is related to the wiki in that they both cover related topics, but I am not an admin of his site, nor is he an admin on this site.<br />
<br />
==Wiki==<br />
Bob, I highly reccomend you to change the comment format from (user-comment) to (comment-user). I think the criticism of the wiki has a bit to do with this. [[User:Dorumagesu|Dorumagesu]] 01:57, 24 May 2008 (UTC)<br />
<br />
Which criticism? I didn't read any. Anyway, I do like the (user-comment) format because it looks a little more like a forum thread-- but I have no strong objection to wikipedia standard (comment-user-date) format. [[User:Bob the Hamster|Bob the Hamster]] 03:22, 24 May 2008 (UTC)<br />
<br />
Oh, some of my classmates say that this wiki is a failure because of the format. [[User:Dorumagesu|Dorumagesu]] 14:15, 24 May 2008 (UTC)<br />
<br />
In an opinion totally unrelated to Dorumagesu's classmates, I think there could be some improvements to the wiki: noticably, it's terribly hard to find anything on the FAQ page. A separate short list of FAQs seems impossible though - which questions should go on it? Or perhaps we should remove some questions and put them somewhere else? (Especially questions about nonexistent bugs, vague questions)<br />
<br />
Something that really worries me is that when people add questions to the FAQ, and we answer them and alphabetize them or rename them, they have no hope of finding the answer without using Recent Changes, which I imagine non-proficient wiki users won't. Maybe we could have a Recent Questions page which is similiar to the old Helpme board format, where people can add questions sequentially to the top and always be able to find the response? We could then copy (possibly a subset of) the questions to the FAQ. Personally, I think that all the questions on the FAQ that only 1 person will ever ask really are detrimental. It's pretty weird to go from a Helpme board where people respond when you answer their question to a wiki, where only about 1 or 2 people in several years have ever responded looking for clarification.<br />
<br />
Finally, I don't entirely like the main page because on [http://nethack.wikia.com/wiki/Main_Page other wikis], the main page welcomes guests and makes for an excellent starting point for finding things; here, it's redundant to the navigation bar. [[User:The Mad Cacti|The Mad Cacti]] 01:53, 25 May 2008 (UTC)<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]]: I vote for a "most recent questions" script, which holds a link to "All Questions (Alphabetical)" for the rest.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]]: I agree that the [[FAQ]] is a mess. [[OLDFAQ]] is a place to move obsolete stuff, I just haven't been vigilant with it. I like to imagine that people use the "search" feature to find things, but I have no idea if that is actually true.<br />
<br />
----<br />
<br />
[[User:Dark Sentiel|Dark Sentiel]]:Hi! Bob, what you did with Skeppio and Rathmara when returned from Jormungand's Belly?<br />
Do you helping James to develop "Wandering Hamster"?<br />
Do you develop own games with OHRRPGCE?<br />
Sorry for stupid questions. I am know English not good.<br />
Thanks<br />
<br />
----<br />
<br />
[[User:The Mad Cacti|The Mad Cacti]]: Hey James, are Recent changes only kept for 14 days? That's really really annoying! I sometimes miss changes that I want to have a look at some other time.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]]: Oh! Funky! I wonder when that started happening? Mediawiki was configured to hide old edits older than 7 days regardless of what settings you chose at the top of [[Special:RecentChanges]]. I found the configuration option and instructed it to rebuild the recent-changes list. You can now go back 8 weeks.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=User:Sorlok_reaves&diff=18657
User:Sorlok reaves
2009-01-20T04:17:13Z
<p>Sorlok reaves: /* Desktop HVM is sorta working... */</p>
<hr />
<div>Just another OHR developer, though my FreeBASIC's a bit rusty.<br />
<br />
They say "those who can't do show off what others ''can'' do." As such, I am working on a version of the OHR that runs on phones, so you can take your favorites on the go. Really, I think the medium is quite suitable.<br />
<br />
Note to James: The "ESC" key in the Netbeans emulator sends an ''unconditional'' kill code to any running Midlets. It always exits the emulator. That said, I managed to trap any conditional kill codes, so it should work on your phone (except that BREW won't let it work at all.) <br />
<br />
[[Image:ohrrpgce_fmf_screenshot.jpg]]<br />
<br />
<br />
Here's a screen-shot of the desktop HVM that I'm working on, as a test of the HVM's core functionality:<br />
<br />
[[Image:Desktop_hvm_screenie.png]]</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=File:Format_b_multiwidth.png&diff=18645
File:Format b multiwidth.png
2009-01-19T03:56:28Z
<p>Sorlok reaves: uploaded a new version of "File:Format b multiwidth.png":&#32;/* Fixed a typo and a green squiggle. */</p>
<hr />
<div>/* Information on Henceforth's "variable width" format. */</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=FMF:HFB&diff=18644
FMF:HFB
2009-01-19T03:50:33Z
<p>Sorlok reaves: /* "All but the simplest" --whoops. This page is now DONE. */</p>
<hr />
<div>{{FMF}}<br />
<br />
<br />
The HFB lump is never encountered in the wild, but its progeny (HF lumps) are used all over the placed in the HVM. Moreover, understanding the "Format B" data stored in HFB lumps is crucial to understanding how the Henceforth Virtual Machine works. <br />
<br />
An HFB lump is simply a full list of "Format B" bytecodes, with no additional data or headers. Format B is designed to be quick to parse, and relatively conservative on space. Most bytecodes are exactly one word (sixteen bits), although some are much longer.<br />
<br />
These lumps are rarely named. If a name is required, you might consider basing the prefix on either the script ID or the script name --for example, '''34.HFB''' or '''calculate_exp.HFB'''. <br />
<br />
<br />
==Legend==<br />
<br />
In the following charts, numbers in <span style="color: green;font-weight:bold">green</span> represent those already accounted for, and numbers in <span style="color: red;font-weight:bold">red</span> represent those currently being described. A star (<span style="color: red;font-weight:bold">*</span>) stands for "any digit". <span style="color: blue;font-weight:bold">Blue</span> digits represent data used by the current block: a period (<span style="color: blue;font-weight:bold">.</span>), question mark (<span style="color: blue;font-weight:bold">?</span>), or a percent sign (<span style="color: blue;font-weight:bold">%</span>) are used to stand for one bit, four bits, and sixteen bits respectively. Numbers in <span style="color: black;font-weight:bold">black</span> can be ignored when considering a specific block. Various highlights are marked and explained on the charts.<br />
<br />
A Vanilla implementation of the HVM need implement the white blocks only; any Pistachio-colored blocks can simply be scanned and ignored. The Pistachio-colored blocks must be implemented by the OHRRPGCE FMF's HVM, and by any HVM that wishes to mimic the game engine.<br />
<br />
<br />
==The First 2 Bits==<br />
<br />
[[Image:Format b toplevel.png]]<br />
<br />
The first 2 bits of each word explain how the remainder of that word is allocated. <br />
*00 is the simplest and fastest to parse; the command is contained within this single word.<br />
*01 implies that this word specifies how many words follow, either by its nature ("I am a two-word integer") or by stating its length as a field in the first word.<br />
*10 is worst to parse; this "variable-width" type requires that parsing continue until a special "magic number" is reached. Although this allows strings to be of arbitrary length (which is better than imposing an artificial limit) it complicates construction of a bare-bones interpreter.<br />
*11 currently has no use, and will cause a parse error.<br />
<br />
<br />
==Single-Width: The Next 4 Bits==<br />
<br />
[[Image:Format_b_singlewidth.png]]<br />
<br />
Single-width bytecodes are relatively simple; they use 4 bits for a control code, and 8 to 10 bits for data or identification.<br />
* 0000 implies the commonly-used "short integer". The right-most remaining 8 bits store the actual value of the integer, from 0 to 255 in value. Negative numbers set the first bit after the control to 1, we call this a "sign bit". <br />
* 0001 implies a Henceforth primitive command, like "pop" or "dup". The remaining 10 bits identify the command; see the table below for the specifics.<br />
* 0010 implies a Hamsterspeak API call. The remaining 10 bits are used for the ID of the API call, as listed [[FMF:Plotscript_Status | here]]. <br />
* 0011 implies user-defined function call; the remaining 10 bits of this bytecode specifty the ID of that function (which is stored in a HF lump somewhere). If the user has more than 1023 functions, they can also be specified with a fixed-width bytecode (see below).<br />
* 0100 implies a script-local subroutine definition. The remaining 10 bits are used for the ID of this routine. <br />
* 0101 implies a call to a script-local subroutine. The remaining 10 bits are used for the ID of the routine to call.<br />
* 0110 implies a global variable usage. The remaining 10 bits are the ID of the global variable to push to the stack. If all 10 bits are 1, the ID is popped from the stack.<br />
* 0111 implies a global variable instantiation. The stack is popped, and stored in the global variable whose id is specified by the remaining 10 bits. If all 10 bits are 1, the ID is popped from the stack.<br />
* 1000 implies a local variable (parameter). The next bit is 0 to push, 1 to pop, and the following bit is a sign bit. The 8 bits following identify the parameter (or return value, if it's negative 1). If all 8 bits and the sign bit are 1, the stack is popped, and that value is used as the ID of the parameter. Popping and pushing may occur on the parent stack, [[Local Parameters & Return Values | as described here]].<br />
* **** (i.e., "anything else) incurs an error from the parser.<br />
<br />
The list of primitives is below:<br />
<br />
{| border="1" cellpadding="5" rules="all"<br />
|bgcolor="#C8FFC8" |'''''ID''''' || bgcolor="#C8FFC8"|primitive ||bgcolor="#C8FFC8" |'''''ID''''' || bgcolor="#C8FFC8"|primitive ||bgcolor="#C8FFC8" |'''''ID''''' || bgcolor="#C8FFC8"|primitive <br />
|-<br />
| 1 || dup || 11 || b_xor || 21 || else_start<br />
|-<br />
| 2 || swap || 12 || b_and || 22 || if_end<br />
|-<br />
| 3 || drop || 13 || eq || 23 || end_define<br />
|-<br />
| 4 || over || 14 || lt || 24 || break<br />
|-<br />
| 5 || rot || 15 || not || 25 || continue<br />
|-<br />
| 6 || add || 16 || and || 26 || break_x<br />
|-<br />
| 7 || sub || 17 || xor || 27 || continue_x<br />
|-<br />
| 8 || mult || 18 || do_start || 28 || b_not<br />
|-<br />
| 9 || div || 19 || do_end || 29 || b_or<br />
|-<br />
| 10 || random || 20 || if_start || 30 || or<br />
|}<br />
<br />
==Fixed-Width: The Next 6 Bits==<br />
<br />
[[Image:Format_b_fixedwidth.png]]<br />
<br />
The next six bits identify the command:<br />
* 000000 implies a double-word integer value follows this word. The next bit is a sign bit, and the two words following contain the magnitude of this integer, from 0 to 4294967296.<br />
* 000001 implies a single-word ID follows this word, which is the ID of a user-defined function to call. Although this can be used to call any function, the single-width version of this bytecode is preferred for smaller number; this is intended as an overflow bytecode.<br />
<br>It should go without saying that anything besides these two bytecodes crashes the system.<br />
<br />
<br />
==Variable-Width: The Next 6 Bits==<br />
<br />
[[Image:Format_b_multiwidth.png]]<br />
<br />
Variable-width bytecodes are interesting. They are specified by the 6 bits following the first 2. The current variable-width bytecodes all revolve around strings, with a distinction for Unicode-free strings designed to save space. (In fact, all strings are converted to Unicode-strings within the HVM). <br />
* 000000 implies an ASCII-style string, although the font in use by the OHR may wildly affect which character is associated with each letter.<br />
* 000011 implies a unicode string. Don't use these just yet; they're nowhere near ready for deployment (mainly because of fonts).<br />
<br />
The second byte in the first word is a "control" field, for describing what to do with this string.<br />
* ''string_define'' &mdash; Declare this as a string; push it into the string table.<br />
* ''begin_define'' &mdash; Start the definition of a subroutine with this string as the '''label'''.<br />
* ''call_subroutine'' &mdash; Call a subroutine whose label is equal to this string.<br />
* ''un-define'' &mdash; Un-define the latest definition of a subroutine with this string as its label.<br />
* ''push_variable'' &mdash; Push a local variable to the stack; the string is the name of this variable.<br />
* ''pop_variable'' &mdash; Pop the stack and store the result in a variable with this string for its name.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=FMF:The_HVM,_From_the_Top_Down&diff=18643
FMF:The HVM, From the Top Down
2009-01-19T03:18:28Z
<p>Sorlok reaves: /* Fixed some inconsistencies; this and bottom-up are now done! */</p>
<hr />
<div>{{FMF}}<br />
<br />
<br />
This page describes the workings of the HVM piece by piece. For your reference, here is a copy of the data structures diagram:<br />
<br />
<br />
[[File:Fmf hvm data structures.png]]<br />
<br />
<br />
Here's the lowdown on each data structure and its elements:<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="50%"<br />
|bgcolor="#C8FFC8" |'''''Global Variable Table''''' || bgcolor="#C8FFC8"|Contains global variables indexed by ID. <br />
|-<br />
| '''ID'''<br />
| ID of the variable (implicit).<br />
|-<br />
| '''Value'''<br />
| Value of the variable (signed integer).<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" |'''''Running Scripts Table''''' || bgcolor="#C8FFC8"|Contains a script UDT and related data for any script which is held in the HVM (running or waiting). <br />
|-<br />
| '''Script'''<br />
| The script UDT for a running script.<br />
|-<br />
| '''Wait'''<br />
| True if the script is not ready to continue execution; false otherwise. <br />
|-<br />
| '''Wait On'''<br />
| A series of bitflags signifying what this script is waiting on, concatenated to the number of cycles left to wait, for wait_cycles. The flags are:<br />
&nbsp;&nbsp;&nbsp;wait_cycles<br />
<br>&nbsp;&nbsp;&nbsp;&nbsp;wait_hero<br />
<br>&nbsp;&nbsp;&nbsp;&nbsp;wait_npcs<br />
<br>&nbsp;&nbsp;&nbsp;&nbsp;wait_anykey<br />
<br>&nbsp;&nbsp;&nbsp;&nbsp;wait_pan<br />
<br>&nbsp;&nbsp;&nbsp;&nbsp;wait_textbox<br />
<br>Note that, in some cases, these flags can all be zero but the script may still be waiting on something.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" |'''''Script UDT''''' || bgcolor="#C8FFC8"|Wraps several fields necessary for a running script. <br />
|-<br />
| '''Local Stack''' (ref)<br />
| With the exception of the "push/pop local" commands, all stack operations will take place on the local stack, allowing for easy multitasking. Conceptually "on top" of the local variable array. Stored as a reference, to allow for the possibility of stack sharing.<br />
|-<br />
| '''Program Counter (PC) Stack'''<br />
| Each entry represents the current index into the "Bytecode" array (see: "Script Cache Pointer Stack") of the bytecode being executed. At the end of every cycle, the top PC entry is incremented by 1. Of course, any PC above index zero must belong to a subroutine, not a user script.<br />
|-<br />
| '''Script Cache Pointer Stack'''<br />
| A series of references to this script's cached data in the "Script Cache" table. If this is ''null'' (or -1), this script UDT's cached data has somehow been lost, and must be re-loaded.<br />
|-<br />
| '''Do/If Stack Stack'''<br />
| A series of stacks of do_UDT entries. One is pushed each time a ''dostart'' or ''ifstart'' imperative is encountered, and popped when the corresponding ''doend'' or ''ifend'' is found. The conglomerate stack is pushed whenever a new subroutine is called.<br />
|-<br />
| '''Local Variable Hash Stack'''<br />
| Named variables such as "x" or "test" are stored in the top-most hash table on this stack conglomerate. Although stack-minded gurus detest local stores, they are allowed into Henceforth for the purpose of clarity when debugging a script at run-time.<br />
|-<br />
| '''ID'''<br />
| Index of this script in the "Script Lookup Table". <br />
|-<br />
| '''Parent Script Pointer'''<br />
| A reference to the script which called this script. When this script terminates, it will "wake" the parent script.<br />
|-<br />
| '''Local Variable Array'''<br />
| Stores the parameters and the return value for a given script. Push and pop operations on these affect the stack of the calling script. Indexing this array at -1 will push an effective "return value", to use standard terminology. Could theoretically be used to clobber the stack (but please don't). <br />
|-<br />
| '''String Table'''<br />
| Contains a list of Unicode-encoded strings, ordered by ID, for use by this script.<br />
|-<br />
| '''Local Function Start Indices'''<br />
| Each script can define local subroutines, which are usually numbered starting from zero, increasing by one monotonically. For example, "\[2] { dup }" defines a local subroutine with ID 2 that serves to duplicate the top of the stack. The interpreter caches the start indices of these '''script-local subroutines''' in a small table for fast lookup.<br />
|-<br />
| '''Return Address Stack'''<br />
| When a script calls a local subroutine, it pushes the current PC to this stack, and pops it upon returning. <br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" |'''''Do UDT''''' || bgcolor="#C8FFC8"|A pair of integers used to manage do loops and if statement. <br />
|-<br />
| '''Start Index'''<br />
| The value of the PC when a do or if statement is encountered will be unique within a script.<br />
|-<br />
| '''Else Index'''<br />
| Contains the index of "else_start" in an if block. (See below). When an entry is pulled from the cache, an end/else index of -1 implies that scanning must occur. -2 indicates that this is a "do" block, not an "if block"; any other number is a valid index in the bytecode array.<br />
|-<br />
| '''End Index'''<br />
| Given a ''do'' loop's start index, the ''break'' command will jump to the next "doend" imperative (taking care to discard any nested "doend"s). Since bytecode is never modified at run-time, this scanning only needs to happen once; the PC index of the "doend" statement can be cached for later. If statements follow the same rule. When an entry is pulled from the cache, an end/else index of -1 implies that scanning must occur; any other number is a valid index in the bytecode array.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" |'''''Script Lookup Table''''' || bgcolor="#C8FFC8"|A full list of all static scripts contains additional data to help retrieve script source from the cache or from the file system. <br />
|-<br />
| '''ID'''<br />
| This field is implicit; all scripts from ID 0 to ID N appear in order in this table. If auto-numbered scripts are used, the Henceforth cross-compiler will renumber them so that there are no gaps in this table.<br />
|-<br />
| '''Name'''<br />
| The name of this script is stored as a string. The Henceforth cross-compiler will always call a static script by its ID, but developers may choose to call a script by name when debugging.<br />
|-<br />
| '''Chunk'''<br />
| Scripts are stored compressed, but compressing a 10-byte file saves you nothing. Hence, there are several scripts "chunked" together in a file. If this value is "5" for example, then the lump "SCRIPTS_5.HF" should be loaded. Please see [[HF | the relevant documentation]] for information on how to extract a particular script from its chunk.<br />
|-<br />
| '''Cache'''<br />
| A reference to this script's entry in the Script Cache, ''null'' or -1 if no entry exists. If several copies of a script exist, they pull only one entry into the cache, and checking this value in the Script Lookup Table is faster than scanning each entry in the Running Scripts Table. Scanning must still occur when an entry is ejected from the Script Cache, but phones with a reasonable amount of memory will rarely have to eject entries.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" |'''''Script Name Multiplexer''''' || bgcolor="#C8FFC8"|A table which holds definitions for named scripts (those defined at runtime).<br />
|-<br />
| '''Name'''<br />
| Unlike static scripts, runtime scripts are identified by name alone. Stored in hash table, no two scripts may have the same name (see below). <br />
|-<br />
| '''Definitions'''<br />
| A stack of script cache entries corresponding to each definition, stored by reference. If, say, the script "exp" is declared twice, the second entry takes precedence until it is undeclared.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" |'''''Script Cache''''' || bgcolor="#C8FFC8"|A table which contains large amounts of recycleable data, to allow our scripts to run on machines with very little memory. Also contains a few flags.<br />
<br>It is recommended that the script cache stores entries by reference (e.g., in a linked list) instead of by index, since this avoids fragmentation & spurious errors.<br />
|-<br />
| '''New Name'''<br />
| Although probably un-necessary, we store the name of the script (or the empty string, for static scripts) to help with debugging. <br />
|-<br />
| '''ID'''<br />
| Stores the ID of a static script, or -1 if this is a runtime script.<br />
|-<br />
| '''Pinned'''<br />
| A flag: if true, this script cannot be removed from memory without throwing the HVM into an invalid state. An example of this would be a runtime-declared script. Theoretically, a static script that is constantly being reloaded from memory or a script that contains a massive Do Table might be manually pinned by the HVM.<br />
|-<br />
| '''In Use'''<br />
| Set to zero initially; incremented by 1 each time a script references this entry. Decremented by 1 each time a script terminates. An entry with an in-use count of 0 is only removed from the Script Cache if more space is needed, since the Do Table is also cached.<br />
|-<br />
| '''Bytecode'''<br />
| An array for the script's '''Format B''' bytecode. This is static data --after being defined once, it can only be re-loaded --never modified.<br />
|-<br />
| '''Do Table'''<br />
| A cache for this script's Do UDTs. When a Script UDT pops its do/it stack, the value is saved here; if the same loop or if statement is encountered again later, a considerable amount of scanning can be skipped.<br />
|}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=HSP2HF&diff=18642
HSP2HF
2009-01-19T03:16:04Z
<p>Sorlok reaves: /* Added a note about optional syntax. This document is done! */</p>
<hr />
<div>{{FMF}}<br />
<br />
Most of you will never have to touch Henceforth if you don't want to. (But who wouldn't want to?) The HSP2HF cross-compiler is used to automatically convert your HamsterSpeak into Henceforth bytecode. It is part of the RPG2XRPG conversion process.<br />
<br />
<br />
<br />
==How the Cross-Compiler Works: Motivations==<br />
<br />
Some parts of the OHRRPGCE FMF project are slightly incompatible with the standard OHRRPGCE, but for scripting this is simply unacceptable. Unlike, say, a slightly jittery enemy HP value -which is immediately apparent when you first load your game on a phone- a silent error in a script is worth hours of headache-inducing debugging, and probably not worth anything at all in the end. <br />
<br />
So, the goal of the cross-compiler is script-level compatibility. Efficiency and conciseness, while important, take a backseat to this driving need.<br />
<br />
<br />
==How the Cross-Compiler Works: Naive Compiler==<br />
<br />
The Henceforth cross-compiler benefits from HamsterSpeak's tree-like structure; a naive conversion can simply convert each node to a local Henceforth function. Consider the following script (from Wandering Hamster, loaded in the HSP2HF utility)<br />
<br />
[[Image:FMF_cross_compiler_screenshot.png]]<br />
<br />
Typical to most HSZ scripts, "setnpcspeed" contains a head "do" node. This node happens to contain only one element, which takes three arguments, each of which is a simple number or variable. Clicking "cross-compile" will invoke the naive converter, which produces the following code:<br />
<br />
\[14]{<br />
[1]@<br />
}<br />
\[12]{<br />
3<br />
}<br />
\[10]{<br />
[0]@<br />
}<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
<br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[4]()<br />
do_end<br />
<br />
Let's start from the local variables section:<br />
@[1]<br />
This is a shorthand syntax; it basically calls "local store", a function deep within the script interpreter which does something like this:<br />
void localStore(int arg) {<br />
local_variables[arg] = pop_parent();<br />
}<br />
<br />
Next, we have the main loop. The "do_start" and "do_end" primitives are there to help the "break" and "continue" primitives to function properly. The meat of the main loop is the call to:<br />
[4]()<br />
...which is simply a call to a script-local subroutine.<br />
<br />
The following code defines script-local subroutine 4:<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
...as three script-local calls([10], [12], and [14]) and one built-in function call ('''H'''amster'''S'''peak:78, alterNPC). The remaining local functions are equally easy to understand. For example, "[1]@" calls "local load" with "1" as an argument. Local load is defined internally as:<br />
void localLoad(arg1) {<br />
push(local_variables[arg1]);<br />
}<br />
<br />
The HSP2HF utility is also an excellent example of a place where recommended syntax is ignored. Although '''alter_npc''' is more readable to humans, '''[HS:78]()''' is a lot easier to parse for a compiler. Likewise, '''do_start''' and '''do_end''' is cleaner machine syntax than '''do{ ... }'''.<br />
<br />
<br />
==How the Cross-Compiler Works: Reasonable Inlining==<br />
<br />
Simple functions like "setnpcspeed" are very easy to inline, just by copying the leaf nodes' source into their parents. The previous script can be re-written as: <br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[0]@<br />
3<br />
[1]@<br />
[HS:78]()<br />
do_end<br />
...which is much more concise. Due to the nature of HF bytecode, inlining usually improves both performance and storage efficiency. (When I have time to profile, I hope to collect some facts to back this up.) However, inlining everything is often either impossible or unwise, which is why one needs a policy for inlining. At the moment, the OHRRPGCE FMF's cross-compiler uses the following algorithm to determine what to inline:<br />
1) Start by doing a naive conversion, and retain the tree structure. <br />
Mark all nodes as "not dead" and with a "count" of 1.<br />
2) Loop until all nodes are inlined or dead. At each iteration, do the following.<br />
3) Determine which nodes are leaf nodes. (Leaf nodes have ''no'' children, or only dead children).<br />
a) If this node cannot be inlined (e.g., it's self-referential, or checks b/c/d/e below fail), <br />
mark it as "dead".<br />
b) If this node's count is "1", inline it. (Copy its corresponding source into any node that <br />
references it, incrementing their "count" value by 1, and then delete it.)<br />
c) If this node's count is "2", and it is referenced 8 times or less, inline it.<br />
d) If this node's count is "3", and it is referenced 4 times or less, inline it.<br />
e) If this node's count is "4" or more, only inline it if it is referenced exactly once.<br />
<br />
We are still discussing what makes a node impossible to inline. Technically, the problem is difficult, but Hamsterspeak byte-code is fairly structured in nature, which means we can probably define a few simple criteria for exclusion. <br />
<br />
<br />
==Primitives & HSPEAK->HF Snippits==<br />
<br />
The cross-compiler inserts snippets of HF code when it encounters an HSPEAK node of a given type. For example, at node 10, given a "number" node with value '''75''', it inserts:<br />
<br />
\[10] {<br />
75<br />
}<br />
<br />
Henceforth, we shall refer to a node of ID n as: '''\[n] {}''' --this allows us to generalize HSPEAK nodes into simple templates. Just a reminder: '''\[n] {}''' represents a ''script-local subroutine''; it is ''not'' valid '''Format T''' syntax.<br />
<br />
The following templates are loaded when the HVM initializes, so the cross-compiler makes use of them to simplify syntax:<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" |'''''Templates'''''<br />
|-<br />
| valign="top" | <span style="color:#0000AA;font-weight:bold">#Template: -2 4 set_var will set local variable 1 to value 4</span><br>\set_var {<br>&nbsp;&nbsp;swap<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;@[]<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;@[.G]<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br><span style="color:#0000AA;font-weight:bold">#Template: 2 get_var will return the contents of global variable 2</span><br>\get_var {<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;[]@<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;[.G]@<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
Here are the snippets used by the cross-compiler; we repeat numbers for the sake of completeness:<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Numbers'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">number</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Do Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | do<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''If Statements'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | if<br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">then_y</span><br><span style="color:green;font-weight:bold">else_z</span><br />
| valign="top" | <br>\[n]{<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[z]()</span><br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Then/Else Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | then/else<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;...<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Break/Continue'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">command</span><br />
| valign="top" | <span style="color:green;font-weight:bold">amount</span><br>''if amount==1 then:''<br>&nbsp;&nbsp;''skip amount''<br>''else:''<br>&nbsp;&nbsp;''append "_x" to command''<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[amount]</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">command</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Returning'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">return</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;''invalid''<br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''While/For'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">while</span><br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;not if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">for</span><br />
| valign="top" | <span style="color:green;font-weight:bold">count_id_x</span><br><span style="color:green;font-weight:bold">counter_start_s</span><br><span style="color:green;font-weight:bold">count_end_e</span><br><span style="color:green;font-weight:bold">counter_step_w</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[s]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;set_var<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;0 gt<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;gt xor not<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;neq and<br>&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;add<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| colspan="4" | '''''Note 1:''''' ''The block <span style="color:green">inline_y</span> simply unrolls the <span style="color:green">do_y</span> block into the body of '''[n]()'''. This is done so that '''break''' and '''continue''' will function properly.'' <br>'''''Note 2:''''' ''The upshot of this is that <span style="color:green">do_y</span> will be instantly culled from the source, unless another node references it (which would be a bit of a hack, in my opinion. Regardless, this is fine, and will not affect program validity in any way.''<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Switch'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | switch<br />
| valign="top" | ???<br />
| valign="top" | ''This is not yet documented in [[HSZ]], so we will deal with it later.<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Variable Access'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">global</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[.G]@<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">local</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[]@<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Math Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">set_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">increment_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"></span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;add<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">decrement_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"></span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;sub<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">not</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;not<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">and</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;[r]()<br>&nbsp;&nbsp;} else { <br>&nbsp;&nbsp;&nbsp;&nbsp;False<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">or</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;not<br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;[r]()<br>&nbsp;&nbsp;} else { <br>&nbsp;&nbsp;&nbsp;&nbsp;True<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">operand</span><br>''If the operand is listed <br>above, use '''that''' code <br>block, not this one.''<br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">operand</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Built-In and User-Defined Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">built-in</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;hspeak_api_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">user-script</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;user_script_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|}<br />
<br />
<br />
<br />
==Resolution Engine==<br />
<br />
After cross-compiling, the HSP2HF utility is basically left with a sequence of bytecodes for each script. The final step is to lump these together into HF lumps. The size of a script, along with its potential to call other scripts, is used to properly group several scripts into one HF lump. To gather this information, a single pass of each script is made. Simultaneously, the resolution engine scans scripts to find simple optimizations which can be performed in place. A list of these optimizations follows.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" |'''''Found''''' <br />
|bgcolor="#C8FFC8" | '''''Replaced By''''' <br />
|bgcolor="#C8FFC8" | '''''Reasoning'''''<br />
|-<br />
| valign="top" | '''number'''&nbsp;&nbsp;get_var<br />
| valign="top" | ['''number'''.G]@ <span style="color:#0000AA;font-weight:bold">&nbsp;&nbsp;#If '''number''' is < 0</span><br> [-('''number'''+1)]@<span style="color:#0000AA;font-weight:bold">&nbsp;&nbsp;#Otherwise</span><br />
| valign="top" | The parameter ID is often known.<br />
|-<br />
| valign="top" | '''number'''&nbsp;&nbsp;'''value'''&nbsp;&nbsp;set_var<br />
| valign="top" | '''value'''&nbsp;&nbsp;@['''number'''.G] <span style="color:#0000AA;font-weight:bold">&nbsp;&nbsp;#If '''number''' is < 0</span><br> '''value'''&nbsp;&nbsp;@[-('''number'''+1)]<span style="color:#0000AA;font-weight:bold">&nbsp;&nbsp;#Otherwise</span><br />
| valign="top" | Ditto to the above.<br />
|}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Talk:System_Requirements&diff=18623
Talk:System Requirements
2009-01-16T08:32:39Z
<p>Sorlok reaves: /* I use Vista, but.... */</p>
<hr />
<div>'''The Geek''': Sorry about the DOS 5.2 thing... I meant DOS 5. And I just realized it might be running slowly because of DriveSpace... I'll turn DriveSpace off and see if that makes any difference.<br />
<br />
== Moving ==<br />
<br />
[[User:The Geek|The Geek]]: Suggestion, maybe this page should be moved to "System Requirements" and have a message added, e.g. "There are no set system requirements, however here is a list of tests performed and how the OHRRPGCE performed" or something like that. Then this page could redirect there.<br />
<br />
[[User:Bob the Hamster|Bob the Hamster]] yeah, I think that is a good idea.<br />
<br />
[[User:The Geek|The Geek]]: OK, I'll see if I can figure out how to move a page, then how to redirect one...<br />
<br />
[[User:The Geek|The Geek]]: Yay, it sets up the redirect automatically! Just wondering, how does one go about adding something to the menu on the left? Methinks this should be there.<br />
<br />
[[User:Pkmnfrk|Mike C.]] You can't. Only [[OHRRPGCE-Wiki:Administrators|Sysops]] (i.e., James, me or TMC) can. But, I think it would belong better on the Downloads page.<br />
<br />
[[User:The Geek|The Geek]]: OK then. And I guess it doesn't really need to be on the sidebar, I was just thinking of the fact that the first thing I look for when I'm investigating a new program is the system requirements.<br />
<br />
[[User:Pkmnfrk|Mike C.]] Ah. My strategy is to download first, and if it doesn't work and/or is for another platform altogether, I blame microsoft.<br />
<br />
[[User:The Geek|The Geek]]: Understood. I guess my reason for always checking the system requirements is I've downloaded so many programs that need more video RAM or a certain framework... it's sooo annoying to download a 20Mb package on dialup and then find you can't run it.<br />
<br />
[[User:Kizul Emeraldfire|Kizul Emeraldfire]]: I've been running the Windows OHR on a Pentium III 650MHz, Windows 98SE computer with 192MB of RAM and it seems to go fine — though, I admit — I haven't tried any heavily-plotscripted games like Baby Bob the Hamster yet. I've pretty much just been using the OHR to make my own games with (my current project is T.M.N.T.: The RPG). :)<br />
<br />
== Rewrite ==<br />
[[User:Bob the Hamster|Bob the Hamster]]: Okay, so in the rewrite, I claimed that Windows XP and Ubuntu Linux are the best-tested operating systems, which is true from mypoint of view, but I would like to update that to reflect the operating systems used by the other devs and by our more prolific power-users.<br />
<br />
[[User:Sorlok reaves|S&#39;orlok Reaves]]: For what it's worth, all my OHR work is done on Vista. But, I only develop tiny games, which I use to test compatibility between the Standard OHR and the OHRRPGCE FMF.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Henceforth_VM&diff=18622
Henceforth VM
2009-01-15T07:55:55Z
<p>Sorlok reaves: /* All sections complete. I'll remove the warning after we get a working script interpreter. */</p>
<hr />
<div>{{FMF}}<br />
<br />
The Henceforth Virtual Machine (HVM, Henceforth VM) is used to dynamically interpret Henceforth (HF, Hamsterforth) scripts. The OHRRPGCE FMF does not use Hamsterspeak's traditional HSX/HSZ files; rather, it opts for a stack-based alternative. Henceforth files are stored with the .HF extension.<br />
<br />
<br />
<div style="border: 2px red solid; width: 600px; padding: 10px"><span style="color:#AA0000; font-weight:bold; font-size: 20pt">Warning: </span> This document is complete, except for the "final considerations" section, which will be trivial to add later.<br>We are currently in the process of reviewing the entire design specification, writing a toy interpreter, and polishing up (and segmenting) this document. <br>We'll gladly take your suggestions on the "Discussion" page, but please do not modify, distribute, or trust this document. It will soon be as open to the community as everything else on this Wiki, but it'll take a few weeks to do the final review.<br><br>Thanks~<br>/Seth<br />
</div><br />
<br />
<br />
=Acknowledgements=<br />
<br />
A big thanks to both James and Ralph for their well-founded suggestions regarding the HVM, and to Mike for clarifying countless issues. If you start reading into the minute details of the HVM, you'll find that a whole lot of it is lean, clean, and sensible. This is almost entirely due to the suggestions and experience of the OHR's patriarchs, which I truly appreciate. <br />
<br />
<br />
=Introduction to Henceforth=<br />
<br />
Henceforth is a simple, stack-based programming language which the OHRRPGCE FMF uses as a crossover language from traditional Hamsterspeak. Like all of the lumps exclusive to this project, .[[HF]] lumps containing Henceforth code are cross-compiled automatically for you when you convert your game to XRPG Format. However, all developers should consider learning basic Henceforth syntax to aid them in writing run-time debug scripts and compile-time optimized versions of their most time-critical Hamsterspeak scripts. <br />
<br />
Henceforth is a stack-based language, designed to obviate register allocation problems and minimize script size. You should read James's [[How_does_plotscripting_work%3F#The_Stack | excellent analogy of a stack]], if you aren't familiar with the concept. Hamsterspeak uses the stack for ordering scripts; Henceforth, however, uses the stack for storing data.<br />
<br />
<br />
==A Henceforth Primer==<br />
<br />
Although Henceforth is not a compiled language, you can pretend that "source" Henceforth is compiled into "binary" HFZ lumps, which the game engine runs, and only [[User:Sorlok_reaves | one person]] will cringe and correct you. So let's have a look at some Henceforth source code, shall we? Any text after a [http://dictionary.reference.com/browse/hash%20mark hash mark] until the next newline is considered a comment, and [http://dictionary.reference.com/browse/whitespace whitespace] is used to separate commands. Consider the following script.<br />
#One way to separate commands is by hitting "Enter" after each command.<br />
4 #Push 4<br />
5 #Push 5<br />
add #Pop the top two elements and add them. Push the result.<br />
@result #Pop the top element and store it in the variable "result".<br />
<br />
#Another way to separate commands is using spaces.<br />
4 5 add @result<br />
Most programmers will use a mix of spaces and newlines to keep their scripts clear and sensible. For example, "4 5 add" should probably always be on the same line. Regardless of what format you use, manipulation of the stack is the same. The following diagram shows how the stack (and the variables list) changes as the HVM reads each command, from left to right.<br />
<br />
[[Image:Fmf_stack_diagram.png]]<br />
<br />
<br />
==The "Pure" Virtual Machine==<br />
<br />
From a theoretical point of view, Henceforth does not require a location to store variables. Rather, we could simply add '''block labels''' and a delayed execution ('''prepending''') command:<br />
* Surrounding some source with '''\name{}''' will label that block with "name"; if '''name''' later appears as a command, this block will be inserted in-place. Later writing '''/name''' will un-define "name". If multiple blocks are labeled similarly, the most-recent label is considered valid. <br />
* Pre-pending "&" to a command will push the result of that command ''into the source code itself'', at the time that command is first encountered. <br />
<br />
'''Block labels''' allow functions to be written very naturally:<br />
#The "modulo" function expects two arguments to be<br />
# on the stack; it transforms (X Y) to (X%Y)<br />
\modulo {<br />
swap dup rot dup rot swap #Now we have (X Y X Y)<br />
div mult sub #The heart of our algorithm<br />
}<br />
There's no need to "return" anything; the labeled block operates in a way that leaves one item on the stack. Here's some code to use this block as a function:<br />
#We want to compute 7%3<br />
7 3 modulo #After this, "1" is left on the stack<br />
<br />
This is, in fact, the exact syntax for writing a function in Henceforth. Now, let's cover '''prepending''':<br />
#Avoiding variables with named blocks.<br />
4 5 add <br />
\result{ #Label the start of the "result" block<br />
&pop #This is tricky: the first time we encounter this<br />
# (i.e., when "\result" is defined), it executes "pop",<br />
# resulting in "9", which is pushed into the source.<br />
} #Closes the "\result" label.<br />
<br />
result #Calling "result" will now push "9".<br />
<br />
Unlike named blocks, prepending is not actually implemented in Henceforth; you can ignore that last bit of code if you don't understand it. Which brings us to our main point:<br />
<br />
<big><big style="color:green;"><br />
'''Henceforth was designed around a "pure" VM'''</big><big>, but it was then modified to </big><big style="color:green;">'''run efficiently'''</big><big> on a phone, and to </big><big style="color:green;">'''allow an easy mapping'''</big><big> from existing Hamsterspeak scripts. <br />
</big></big><br />
<br />
It is crucial that you keep the ideal virtual machine in mind, or else you will start asking about "Henceforth's registers" (which don't exist) or you'll start using variables way too much. You also need to be cognizant of the dirty details required to kludge Hamsterspeak onto a phone; otherwise, things like script-level subroutines and our extravagant caching mechanism will seem redundant and senseless.<br />
<br />
(Note to readers: At this point, if you're only interested in the Henceforth language, you can skip down to the section on [[Henceforth_VM#Henceforth Syntax Dictionary | Henceforth Syntax]].)<br />
<br />
<br />
==Where the HVM Fits Into the Game Loop==<br />
<br />
Let's pretend that on a certain map in your game, a certain NPC is set up to "run script 4" when the user walks up to it and presses "Accept". Upon pressing "Accept", the game engine checks if this NPC has a script ID assigned to its "accept" behavior. The classic OHR performs this same task. If one exists, it is initialized and cached. At this point, the game engine continues with its tasks for that cycle (e.g., message boxes, vehicles). This (including user input) constitutes the "Update" phase of the game loop. Following this is the "Paint" phase, which shows one frame's worth of updates to the user. After painting, the tick count is incremented, and the loop repeats. The first thing that happens then is the "scripts" phase, during which scripts are actually executed (see below). The loop repeats indefinitely.<br />
<br />
[[Image:Hf_flowchart.png]]<br />
<br />
<br />
<br />
===Some Technical Considerations===<br />
<br />
When a script is triggered by any means, it suspends the currently-executing script; see "The Stack" in "[[How does plotscripting work?]]". This makes sense; if your script calls another script, one expects the new script to take over. It also means that if you step on several map tiles which each have (long) scripts, the last maptile's script will finish first. (Threading will probably change this.)<br />
<br />
But wait, there's more! Several small but important details of script initialization include:<br />
# If you trigger the same script twice (e.g., by stepping on two copies of an NPC one after the other) the script loader will detect that you are trying to load a script with the '''same id''' as the currently executing script. If the "Permit double-triggering of scripts" general bitset is on (or if the script is explicitly called by ''another script'') then this is allowed, and a second instance of the script is initialized. <br />
# When a script instance is '''first''' loaded, its "delay" counter is set to 2 ticks. This is ''not'' the same thing as instructing the script to "wait", because it remains the active script while delaying. If another script pre-empts this one, the delay counter does not decrement until the new script finishes. (When the new script finishes, the old one immediately resumes what it was doing, be it running or decrementing the "delay" counter.) <br />
# To prepare for parallel scripts, the active script is pushed to the top of the "waiting" stack, and is manipulated by a reference ID. This way, all scripts are kept in one place, and a future, parallel version of GAME_FMF can just scan through this list once each cycle, updating all scripts until they enter a waiting state.<br />
<br />
<br />
<br />
<br />
=Virtual Hardware Specification=<br />
<br />
The [http://en.wikipedia.org/wiki/Virtual_machine Wikipedia page] for Virtual Machines focuses on their use in emulating existing hardware. In fact, there is no hardware version of the HVM, just like there is no hardware equivalent of the Java Virtual Machine. Rather, Henceforth defines an unambiguous virtual hardware specification which can be ported to any platform with guaranteed interoperability. Currently, the only platform with a working HVM is the OHRRPGCEFMF, due to the bias of the HVM's design towards mobile devices.<br />
<br />
Anything in the world can be described by its nouns and verbs --the things it '''has''' and what they '''do'''. So let's inspect the '''pieces''' of the HVM and how they '''interact'''.<br />
<br />
<br />
==VM Data Structures==<br />
<br />
[[Image:Fmf_hvm_data_structures.png]]<br />
<br />
Green and red lines in this diagram indicate references from one item to another, although they are not drawn for every single reference (because that would be craaaaaaazy). <br />
<br />
The Script UDT block is particularly garish, because it is accommodating two different script types: user-defined scripts and named subroutines. Each component is colored differently depending on its use.<br />
<br />
<br />
=== What these do, top-down ===<br />
<br />
One way of learning how these data structures work is by discussing what each piece does on its own, and then assuming their combination. This is like studying one of those [http://www.applesource.com/peeler.html apple machines]; we say that this piece pierces the apple and holds it, that piece slices the skin off, and that blade cores the apple.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_top-down.png|link=FMF:The_HVM,_From_the_Top_Down]] || valign="top" | '''[[FMF:The_HVM,_From_the_Top_Down | Top-Down Description of the HVM]]'''<br>Read on for a detailed summary of the HVM's parts.<br />
|}<br />
<br />
<br />
<br />
===What These Do: Bottom-Up Description===<br />
<br />
If you've ever seen an [http://www.applesource.com/peeler.html apple machine] like the one just mentioned, you're probably aware that describing each element does absolutely nothing to help you understand how the thing works --you have to actually use it on an apple (preferably very slowly) to see how the magic is done. This is the essence of a bottom-up description: describe how certain parts '''interact''' at critical moments in the operation of the whole.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_bottom-up.png|link=FMF:The_HVM,_From_the_Bottom_Up]] || valign="top" | '''[[FMF:The_HVM,_From_the_Bottom_Up | Bottom-Up Description of the HVM]]'''<br>Read on for a detailed summary of how the HVM's parts interact.<br />
|}<br />
<br />
<br />
<br />
<br />
=HF Source Code & Formats=<br />
<br />
Strictly speaking, Henceforth has three different formats for its source code, and there is no such thing as "compiled Henceforth". These three formats are '''Format T''' (text-based), '''Format B''' (bytecode), and '''Format HF''' (compressed). Practically, most user code is generated in '''Format T''', which is then converted to '''Format B''' and compressed (to '''Format HF''') for storage. The HVM reads all three formats, but in doing so it converts T & HF to B. <br />
<br />
That said, we shall begin our study of Henceforth syntax by exhaustively reviewing all Henceforth commands in "source code" format ('''Format T'''), and then describe the mappings to other formats. A few commands cannot be explicitly represented in text format, as the following table details. We shall provide a recommended way of typing these when possible.<br />
<br />
{| cellspacing=0<br />
| &nbsp; || style="border: 1px solid black;" | '''Type''' || style="border: 1px solid black;" | '''Use'''<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format T || valign="top" style="border: 1px solid black;" | text || valign="top" style="border: 1px solid black;" | All human interaction (coding, discussing ideas, etc.) occurs in this format. Thins like script-local subroutines and function parameters cannot be expressed explicitly in this format.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format B || valign="top" style="border: 1px solid black;" | bytecode (binary) || valign="top" style="border: 1px solid black;" | The HVM executes Format B code. The HSP2HF cross-compiler outputs this format. Contains Henceforth Extensions for Scripting.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format HF || valign="top" style="border: 1px solid black;" | compressed || valign="top" style="border: 1px solid black;" | The XRPG specification requires all Format B code to be compressed into format HF lumps, which are then stored locally on the user's phone.<br />
|}<br />
<br />
Henceforth source is a rather slim language. Numbers and strings are supported, but concatenations like "Hello"+" world" and decimals like 2.4 are not. A syntax exists for "do" and "if" statements, and for declaring functions and variables. Several smaller hacks exist for efficiency's sake, as described earlier. And that's it. Anything else is either a "primitive" like '''add''', or a "block label", subject to the polymorphic lookup that characterizes Forth-like languages. Henceforth is not based explicitly on Classic Forth, but it hopes to learn from the mistakes of this peculiar family of languages. One of the main disadvantages to ANSI Forth is its pandering to ANSI C fixations, overcomplicating a language which, at its heart, always championed simplicity and elegance.<br />
<br />
<br />
==Format B Bytecode Specification (HFB)==<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_format-b.png|link=FMF:HFB]] || valign="top" | '''[[FMF:HFB | Format B Specification]]'''<br>Format B code is detailed into exhaustion on a separate page.<br />
|}<br />
<br />
<br />
<br />
<br />
==Henceforth Syntax Dictionary==<br />
<br />
This section contains all of the commands that you could enter into a run-time interpreter (if such a one existed). There is no reference to HSpeak scripts, GAME API calls, or anything else; this section simply explains Henceforth as a language.<br />
<br />
Think of it this way: A Henceforth "script" (called a '''subroutine''') is a slimmed-down version of a Henceforth HSpeak "script" (called a '''script'''). A script maintains a separate stack, for one thing, and script-local subroutines, for another. So, by way of example, calling "wait" within a subroutine would cause the HVM to crash. A script cannot be specified in textual format -that's only a recommended part of the specification, not required. A global script called "root" is used to load subroutines which would otherwise have no parent script.<br />
<br />
<br />
===Numbers===<br />
<br />
Numbers are represented by signed integers such as 2, 18, -35, or 43. When the HVM encounters a number, it pushes it to the stack. That's all for numbers, so let's cover stack effect diagrams briefly; they're necessary to understand more complicated features. Consider:<br />
{| border="1" cellpadding="3" rules="all"<br />
|-<br />
| <tt style="font-size:10pt">1 2 3</tt>|| <tt style="font-size:10pt">7</tt> || <tt style="font-size:10pt">1 2 3 7</tt><br />
|}<br />
<br />
(Note that stack-effect diagrams are oriented with the top of the stack on the right.)<br />
<br />
This diagram implies that the stack starts with the elements 1, 2, and 3 already in it, is then given the command "7", and the resulting stack is now 1, 2, 3, then 7. These is an example of the ''prototype'' style of learning; to teach you how to use a command, I give you a real-world example of how it works. <br />
<br />
If I want to write a stack effect diagram without the fancy table, I might use parentheses and the "--" symbol to mean "the operator we're talking about". For example: "if the interpreter encounters -5, the resulting stack-effect diagram would be <tt style="font-size:10pt">(1 2 3 -- 1 2 3 -5)</tt>". <br />
<br />
For convenience, hexadecimal numbers are allowed, with a '''0x''' prefix. Internally, however, they are simply stored as decimals.<br />
<br />
<br />
===Primitives===<br />
<br />
When the HVM interprets a literal string like '''exp''', '''mycode''', or '''abs''', it searches for the relevant subroutine. The primitives are exceptions to this; they are always executed immediately. A primitive consists of lowercase letters and underscores, and must contain at least one letter. We call this combination its '''symbol name'''.<br />
<br />
Here is a list of primitives, and their stack-effect diagrams:<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Primitive''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="5"| Stack Manipulation || <tt style="font-size:10pt">dup</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 3 )</tt> || "Duplicate" the top of the stack.<br />
|-<br />
| <tt style="font-size:10pt">swap</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 3 2 )</tt> || "Swap" the top two elements of the stack.<br />
|-<br />
| <tt style="font-size:10pt">drop</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 )</tt> || "Drop" the top element off the stack.<br />
|-<br />
| <tt style="font-size:10pt">over</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 2 )</tt> || Pull the penultimate element "over" the top element.<br />
|-<br />
| <tt style="font-size:10pt">rot</tt> || <tt style="font-size:10pt">( 1 2 3 4 -- 1 4 3 2 )</tt> || "Rotate" the top three elements of the stack.<br />
|-<br />
| rowspan="5"| Arithmetic || <tt style="font-size:10pt">add</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 5 )</tt> || Addition<br />
|-<br />
| <tt style="font-size:10pt">sub</tt> || <tt style="font-size:10pt">( 7 3 2 -- 7 1 )</tt> || Subtraction<br />
|-<br />
| <tt style="font-size:10pt">mult</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 6 )</tt> || Multiplication<br />
|-<br />
| <tt style="font-size:10pt">div</tt> || <tt style="font-size:10pt">( 1 7 3 -- 1 2 )</tt> || Division<br />
|-<br />
| <tt style="font-size:10pt">random</tt> || <tt style="font-size:10pt">( 1 4 8 -- 1 6 )</tt> || Push a random number to the stack.<br />
|-<br />
| rowspan="4"| Bitwise Operators || <tt style="font-size:10pt">b_xor</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 17 )</tt> || Exclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_or</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 53 )</tt> || Inclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_and</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 36 )</tt> || Bitwise "And"<br />
|-<br />
| <tt style="font-size:10pt">b_not</tt> || <tt style="font-size:10pt">( 1 52 -- 1 4294967243 )</tt> || Bitwise "Not"<br />
|-<br />
| rowspan="6"| Logical Operators || <tt style="font-size:10pt">eq</tt> || <tt style="font-size:10pt">( 1 2 2 -- 1 1 )</tt> || Logical "Equals"<br />
|-<br />
| <tt style="font-size:10pt">lt</tt> || <tt style="font-size:10pt">( 1 5 2 -- 1 0 )</tt> || Logical "Less Than"<br />
|-<br />
| <tt style="font-size:10pt">not</tt> || <tt style="font-size:10pt">( 1 5 1 -- 1 5 0 )</tt> || Logical "Not"<br />
|-<br />
| <tt style="font-size:10pt">and</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 1 )</tt> || Logical "And"<br />
|-<br />
| <tt style="font-size:10pt">or</tt> || <tt style="font-size:10pt">( 1 5 0 1 -- 1 5 1 )</tt> || Inclusive Logical "Or"<br />
|-<br />
| <tt style="font-size:10pt">xor</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 0 )</tt> || Exclusive Logical "Or"<br />
|}<br />
<br />
There is no boolean type; the number zero is considered "false", and any other number is considered "true". For clarity, the number "1" is the preferred way to represent "true".<br />
<br />
The primitives "and" and "or" do ''not'' short-circuit. This is because a short-circuiting operator generates an ambiguous stack-effect diagram, and achieves no gain in performance or security (since it operates on values which have already been computed.) The [[HSP2HF | Cross-Compiler]] has an example of how to perform short-circuiting calculations in a manual way; see the section on Mathematical Functions.<br />
<br />
A number of additional keywords such as '''break''' and '''continue''' will be discussed in the section on If statements and Loops.<br />
<br />
===Blocks (Subroutines)===<br />
<br />
Labeled blocks, also called subroutines, are the core of Henceforth's modularization. They are strikingly similar to primitives, except that they can be defined, undefined, and overloaded. Labels, like primitives, are identified by a '''symbol name'''.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This creates a function called "mod", which performs a series of stack manipulations and basic arithmetic.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;7 3 mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Traditionally, we push the "parameters" to the subroutine in increasing order, and expect it to remove these arguments (and replace them with a return value, if any) during the course of its operation.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Overriding a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;drop drop 0<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;After executing this snippet, "mod" will have two definitions. Calling "mod" will result in the latter being evoked, which simply clears the parameters and returns zero.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Un-defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">/NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;/mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;If we execute the code from "Overriding a Subroutine", and then execute this code, "mod" will be left with a single definition: the first one.<br />
|}<br />
<br />
<br />
Any labeled block remains in effect even if the script which defined it finishes execution; make sure to un-define any subroutines you do not wish to linger. The HVM capitalizes on this behavior to limit the number of built-in primitives. The modulus operator described earlier is an example, as is "less than or equal to", which can be defined compositely. Here is a list of all the subroutines the HVM loads by default, with their stack-effect diagrams.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Subroutine''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="2"| Arithmetic || <tt style="font-size:10pt">exp</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 8 )</tt> || Compute x raised to the y ("exponent")<br />
|-<br />
| <tt style="font-size:10pt">mod</tt> || <tt style="font-size:10pt">( 1 10 4 -- 1 2 )</tt> || Calculate the "modulus"<br />
|-<br />
| rowspan="4"| Logical Operators || <tt style="font-size:10pt">neq</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 1 )</tt> || Logical "Not Equal To"<br />
|-<br />
| <tt style="font-size:10pt">lte</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 1 )</tt> || Logical "Less Than or Equal To"<br />
|-<br />
| <tt style="font-size:10pt">gt</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 0 )</tt> || Logical "Greater Than"<br />
|-<br />
| <tt style="font-size:10pt">gte</tt> || <tt style="font-size:10pt">( 1 63 45 -- 1 1 )</tt> || Logical "Greater Than or Equal To"<br />
|-<br />
| rowspan="2"| Convenience Methods || <tt style="font-size:10pt">true</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 45 52 1 )</tt> || Locus-of-control for "true"<br />
|-<br />
| <tt style="font-size:10pt">false</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 45 45 0 )</tt> || Locus-of-control for "false"<br />
|}<br />
<br />
<br />
===Variables===<br />
<br />
Shuffling the stack beyond the fourth element can get messy. As such, subroutines can allocate ''local variables'' to the scope of the parent '''script''' they are running in. Note that variables follow a similar naming convention to subroutines. They are accessed using the @VAR and VAR@ commands. You can remember them easily by putting an imaginary "stack" next to them: <br />
* '''(stack)@VAR''' takes a value from the stack and throws it '''at''' VAR. This is the same as setting VAR = pop().<br />
* '''VAR@(stack)''' takes the value in VAR and throws it '''at''' the stack. This is the same as pushing VAR.<br />
<br />
Here is some sample source for the '''lte''' function, written using variables. Note that, in reality, '''lte''' only uses the stack. This is much more efficient for the HVM, which only initializes the local variable store if it detects your script trying to access it.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using Variables'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\lte{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Pop the stack twice to get our local variables.<br>&nbsp;&nbsp;&nbsp;&nbsp;#We assume that we are testing @lhs <= @rhs<br>&nbsp;&nbsp;&nbsp;&nbsp;@rhs&nbsp;&nbsp;@lhs<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the first test (<) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res1<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;lt&nbsp;&nbsp;@res1<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the second test (==) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res2<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;eq&nbsp;&nbsp;@res2<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our two tests and do an or test. Leave it on<br>&nbsp;&nbsp;&nbsp;&nbsp;# the stack to be returned<br>&nbsp;&nbsp;&nbsp;&nbsp;res1@&nbsp;&nbsp;res2@&nbsp;&nbsp;or<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;As an exercise, you should try to convert this to a stack-based alternative. It should end up more concise, and also faster. (Profile it, to be sure).<br />
|}<br />
<br />
<br />
===Do/If & Loops===<br />
<br />
Most Forth-like languages have an "if" statement, and Henceforth is no exception. However, rather than encapsulating loops into labeled blocks (like Classic Forth), it adds another keyword: "do". This serves two purposes: first, it allows for better inlining than subroutines, and second, it provides a locus of control for jump commands. The second rationale is sometimes written as "otherwise, we'd have to use '''goto'''".<br />
<br />
Inside of a do loop, the commands '''break''' and '''continue''' also take effect; you should read [[HSZ#Id_11:_break | the HSZ documentation]] for an example of how these work conceptually. The mechanism for '''break''' and '''continue''' is different in the HVM, but the effect is the same.<br />
<br />
The standard Hamsterspeak engine also allows breaking out of multiple loops at once. In order to keep the syntax clean in Henceforth, we introduce two bytecodes for that purpose: '''break_x''' and '''continue_x'''. These take the element from the top of the stack, and break out of that many loops. The '''break''' command is effectively equivalent to "1 break_x". <br />
<br />
Thus, we add five more keywords to the list of Henceforth primitives: '''do''', '''if''', '''else''', '''break''', and '''continue'''. Brackets are used to denote code blocks where relevant. We will cover the syntax in simple examples. The first is a user-defined subroutine, "divide_safe", which avoids dividing by zero, and the second is an example of the Fibonacci sequence using "do" to achieve the effect of a "while" loop.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Save Divide-by-Zero Handling'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\divide_safe{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Do a zero-check on the divisor<br>&nbsp;&nbsp;&nbsp;&nbsp;dup&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;#Now, branch on that<br>&nbsp;&nbsp;&nbsp;&nbsp;if{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pop&nbsp;&nbsp;pop&nbsp;&nbsp;0<br>&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;div<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Note that returning zero is just one way of handling the erroneous case. Also, take note of the two "pop" commands; these are required!<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Fibonacci Numbers'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\fibonacci{<br>&nbsp;&nbsp;&nbsp;&nbsp;@count&nbsp;&nbsp;&nbsp;&nbsp; #Must be >=1<br>&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;@fprev&nbsp;&nbsp;#fib(n-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;@fnow&nbsp;&nbsp; #fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Test-and-decrement<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;-1&nbsp;&nbsp;add&nbsp;&nbsp;@count<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Iterate & Accumulate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprev@&nbsp;&nbsp;fnow@&nbsp;&nbsp; add<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fnow@&nbsp;&nbsp; @fprev<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fnow<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Re-do the loop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fnow@<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This subroutine fails given zero as input. Modify it by adding a simple if statement before the main loop. <br />
|}<br />
<br />
This second example is far more interesting, since it answers the question "How do I Loop in Henceforth?"<br />
<br />
'''''A Note About Recursion''''': Although Henceforth fully supports recursion, the HVM is not designed for heavily-recursive functions. When possible, please use an iterative solution.<br />
<br />
<br />
<br />
==Henceforth Extensions for Scripting==<br />
<br />
The Henceforth specification recommends (read: ''does not require'') that the following pseudo-syntax be adopted to describe the commands which appear only in the '''Format B''' code for HSpeak script equivalents. A vanilla HVM should recognize these tokens and either ignore them, simulate them, or generate an informative error message when it encounters them.<br />
<br />
Backwards compatibility need not be preserved when converting between the '''Format B''' and '''Format T''' equivalents for script extensions.<br />
<br />
<br />
<br />
===HSpeak API Calls & User Scripts===<br />
<br />
The HVM has knowledge of most "built-in" functions that Hamsterspeak provides. These are called like any other subroutine, with one exception: most format converters create specialized subroutine calls out of them. In other words, if I write a call to '''wait_npc''' in my ('''Format T''') code, the converter will create ('''Format B''') source that points ''explicitly'' to the definition of '''wait_npc''' in the HVM; it is '''''not''''' possible to overload '''wait_npc'''. This breaks our promise of fully-interchangeable source formats, but it is hardly noticeable to most users, and keeps our global function lookup table from becoming too cluttered.<br />
<br />
See [[FMF:Plotdict]] for a listing of all API calls and their syntax in Henceforth.<br />
<br />
User-defined scripts are treated exactly like API calls; they are assigned IDs and called explicitly. <br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Calling an HSpeak API Method'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;12<br>&nbsp;&nbsp;show_txt</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Shows text box with ID 12. The api call '''show_txt''' is the shorthand for "show text box", [[FMF:Plotdict | as listed here]]. <br />
|}<br />
<br />
<br />
===Strings===<br />
<br />
Strings in Classic Forth are handled by simply pushing a series of integers (for each letter) to the stack. Eventually, Henceforth may go this route, but for now the API calls to strings are so simple that we chose a much simpler implementation:<br />
* Strings are a series of letters between double-quotes, like "Hello World", "Going?", or "Unicode: \u1000". As hinted by the last example, the HVM supports Unicode.<br />
* When a string is encountered (usually at the top of a script) it is pushed to the '''String Table''' for that script. Strings are pushed in order, cannot be removed or easily manipulated, and have IDs in increasing order.<br />
<br />
This is an ''ad hoc'' implementation of strings, until we get a clearer picture where the standard OHR's development is going with these ideas.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using a String'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;"Hello world!"<br>&nbsp;&nbsp;0 show_str</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Defining "Hello world!" will place this string at index zero; pushing 0 and then calling the API function '''show_str''' will reference that string explicitly.<br />
|}<br />
<br />
<br />
===Script-Local Subroutines===<br />
<br />
Here's where Henceforth starts to really get picky. When a Hamsterspeak script is converted to Henceforth, it is basically converted into a series of labeled blocks that call each other. The keen observer will note that representing these blocks as subroutines is inefficient, since they can be consistently enumerated. <br />
<br />
With that in mind, subroutines are allowed to define "script-local" subroutines which operate along a single thread of control within a named block. These are defined with a given ID, and called with an array-like syntax. They cannot be un-defined or overloaded, although theoretically they could be replaced (by re-defining them).<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\[ID]{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\[5]{<br>&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;plus<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Our "plus two" example from before, assigned to index 5.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">[ID]()</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;16&nbsp;&nbsp;[5]()</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This pushes 16, and calls local block 5. The result is a stack containing the number 18, if we define [5] as listed above. ''The parentheses, although not technically necessary, do a great deal to visually distinguish script-local subroutines from function parameters.''<br />
|}<br />
<br />
<br />
===Local Parameters & Return Values===<br />
<br />
'''''MAKE A NOTE: Give an example of how my_script{ exp } calls "exp" ON the stack of my_script. '''''<br />
<br />
An astute reader will have noticed that scripts currently have no way of passing values to other scripts. Calling "5 6 my_func" won't work, because '''my_func''' will get its own independent stack. This problem is circumvented through the use of local parameters, which also provide a slight speedup for small, frequently called subroutines. The local parameters are accessed by ID, with -1 a special ID that refers to the script's return value. <br />
* Executing @[5] will "pop the (parent) stack to local parameter 5"<br />
* Executing [5]@ will "push local parameter 5 onto the (current) stack<br />
For the return value, it's backwards:<br />
* Executing @[-1] will "pop the (current) stack to the return value"<br />
* Executing [-1]@ will "push the return value onto the (parent) stack", '''''unless''''' the return value is set to ''invalid'' (INT_MIN, for now).<br />
The only point of interest is that last bit. The problem is, the HSpeak command "return" sets the return value, and "exitscript" will exit and return that value. Some scripts simply don't set a return value, so the HVM pushes the '''most recently computed integer''' if it detects an ''invalid'' return value.<br />
<br />
===Comments===<br />
<br />
Henceforth supports comments, although it's important to remember that Formats B and HF will drop your comments; they can't be encoded. A comment begins with a hash mark (#) and ends with a line feed. <br />
<br />
<br />
===Global Variables===<br />
<br />
All that's left are global variables, which are manipulated with syntax like '''@[65.G]''' and '''[65.G]@'''. We realize this is a bit awkward, and we're open to a cleaner syntax (once we actually get a release going).<br />
<br />
<br />
<br />
= The Cross-Compiler =<br />
<br />
The OHRRPGCE FMF provides a tool to help convert existing Hamster Speak scripts to Henceforth lumps; read about it here:<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_hsp2hf.png|link=HSP2HF]] || valign="top" | '''[[HSP2HF | The HSP2HF Cross-Compiler]]'''<br>Read on for details about this tool.<br />
|}<br />
<br />
<br />
=Final Considerations=<br />
<br />
Henceforth is a big step away from Hamsterspeak, and it is best to approach it with an open mind. Try solving [http://projecteuler.net/index.php?section=problems common problems] with snippets of Henceforth; even if you simply write the solution on paper it will help you get used to thinking in a stack-based fashion. <br />
<br />
The developers of Henceforth are always interested in advancing the language; feel free to post a message [[User_talk:Sorlok_reaves | on Seth's page]] with your ideas --except, try to avoid buzzwords. Henceforth will never have objects, threading (which is ''different'' from multi-tasking), or static typing; it's just not that kinda language.</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=File:Hvm_thumb_hsp2hf.png&diff=18621
File:Hvm thumb hsp2hf.png
2009-01-15T07:51:42Z
<p>Sorlok reaves: /* Thumbnail for a link to the HSP2HF cross-compiler. */</p>
<hr />
<div>/* Thumbnail for a link to the HSP2HF cross-compiler. */</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Henceforth_VM&diff=18620
Henceforth VM
2009-01-15T07:36:24Z
<p>Sorlok reaves: /* Shuffled a few bits of this page around, mostly relating to bytecode. */</p>
<hr />
<div>{{FMF}}<br />
<br />
The Henceforth Virtual Machine (HVM, Henceforth VM) is used to dynamically interpret Henceforth (HF, Hamsterforth) scripts. The OHRRPGCE FMF does not use Hamsterspeak's traditional HSX/HSZ files; rather, it opts for a stack-based alternative. Henceforth files are stored with the .HF extension.<br />
<br />
<br />
<div style="border: 2px red solid; width: 600px; padding: 10px"><span style="color:#AA0000; font-weight:bold; font-size: 20pt">Warning: </span> This document is complete, except for the "final considerations" section, which will be trivial to add later.<br>We are currently in the process of reviewing the entire design specification, writing a toy interpreter, and polishing up (and segmenting) this document. <br>We'll gladly take your suggestions on the "Discussion" page, but please do not modify, distribute, or trust this document. It will soon be as open to the community as everything else on this Wiki, but it'll take a few weeks to do the final review.<br><br>Thanks~<br>/Seth<br />
</div><br />
<br />
<br />
=Acknowledgements=<br />
<br />
A big thanks to both James and Ralph for their well-founded suggestions regarding the HVM, and to Mike for clarifying countless issues. If you start reading into the minute details of the HVM, you'll find that a whole lot of it is lean, clean, and sensible. This is almost entirely due to the suggestions and experience of the OHR's patriarchs, which I truly appreciate. <br />
<br />
<br />
=Introduction to Henceforth=<br />
<br />
Henceforth is a simple, stack-based programming language which the OHRRPGCE FMF uses as a crossover language from traditional Hamsterspeak. Like all of the lumps exclusive to this project, .[[HF]] lumps containing Henceforth code are cross-compiled automatically for you when you convert your game to XRPG Format. However, all developers should consider learning basic Henceforth syntax to aid them in writing run-time debug scripts and compile-time optimized versions of their most time-critical Hamsterspeak scripts. <br />
<br />
Henceforth is a stack-based language, designed to obviate register allocation problems and minimize script size. You should read James's [[How_does_plotscripting_work%3F#The_Stack | excellent analogy of a stack]], if you aren't familiar with the concept. Hamsterspeak uses the stack for ordering scripts; Henceforth, however, uses the stack for storing data.<br />
<br />
<br />
==A Henceforth Primer==<br />
<br />
Although Henceforth is not a compiled language, you can pretend that "source" Henceforth is compiled into "binary" HFZ lumps, which the game engine runs, and only [[User:Sorlok_reaves | one person]] will cringe and correct you. So let's have a look at some Henceforth source code, shall we? Any text after a [http://dictionary.reference.com/browse/hash%20mark hash mark] until the next newline is considered a comment, and [http://dictionary.reference.com/browse/whitespace whitespace] is used to separate commands. Consider the following script.<br />
#One way to separate commands is by hitting "Enter" after each command.<br />
4 #Push 4<br />
5 #Push 5<br />
add #Pop the top two elements and add them. Push the result.<br />
@result #Pop the top element and store it in the variable "result".<br />
<br />
#Another way to separate commands is using spaces.<br />
4 5 add @result<br />
Most programmers will use a mix of spaces and newlines to keep their scripts clear and sensible. For example, "4 5 add" should probably always be on the same line. Regardless of what format you use, manipulation of the stack is the same. The following diagram shows how the stack (and the variables list) changes as the HVM reads each command, from left to right.<br />
<br />
[[Image:Fmf_stack_diagram.png]]<br />
<br />
<br />
==The "Pure" Virtual Machine==<br />
<br />
From a theoretical point of view, Henceforth does not require a location to store variables. Rather, we could simply add '''block labels''' and a delayed execution ('''prepending''') command:<br />
* Surrounding some source with '''\name{}''' will label that block with "name"; if '''name''' later appears as a command, this block will be inserted in-place. Later writing '''/name''' will un-define "name". If multiple blocks are labeled similarly, the most-recent label is considered valid. <br />
* Pre-pending "&" to a command will push the result of that command ''into the source code itself'', at the time that command is first encountered. <br />
<br />
'''Block labels''' allow functions to be written very naturally:<br />
#The "modulo" function expects two arguments to be<br />
# on the stack; it transforms (X Y) to (X%Y)<br />
\modulo {<br />
swap dup rot dup rot swap #Now we have (X Y X Y)<br />
div mult sub #The heart of our algorithm<br />
}<br />
There's no need to "return" anything; the labeled block operates in a way that leaves one item on the stack. Here's some code to use this block as a function:<br />
#We want to compute 7%3<br />
7 3 modulo #After this, "1" is left on the stack<br />
<br />
This is, in fact, the exact syntax for writing a function in Henceforth. Now, let's cover '''prepending''':<br />
#Avoiding variables with named blocks.<br />
4 5 add <br />
\result{ #Label the start of the "result" block<br />
&pop #This is tricky: the first time we encounter this<br />
# (i.e., when "\result" is defined), it executes "pop",<br />
# resulting in "9", which is pushed into the source.<br />
} #Closes the "\result" label.<br />
<br />
result #Calling "result" will now push "9".<br />
<br />
Unlike named blocks, prepending is not actually implemented in Henceforth; you can ignore that last bit of code if you don't understand it. Which brings us to our main point:<br />
<br />
<big><big style="color:green;"><br />
'''Henceforth was designed around a "pure" VM'''</big><big>, but it was then modified to </big><big style="color:green;">'''run efficiently'''</big><big> on a phone, and to </big><big style="color:green;">'''allow an easy mapping'''</big><big> from existing Hamsterspeak scripts. <br />
</big></big><br />
<br />
It is crucial that you keep the ideal virtual machine in mind, or else you will start asking about "Henceforth's registers" (which don't exist) or you'll start using variables way too much. You also need to be cognizant of the dirty details required to kludge Hamsterspeak onto a phone; otherwise, things like script-level subroutines and our extravagant caching mechanism will seem redundant and senseless.<br />
<br />
(Note to readers: At this point, if you're only interested in the Henceforth language, you can skip down to the section on [[Henceforth_VM#Henceforth Syntax Dictionary | Henceforth Syntax]].)<br />
<br />
<br />
==Where the HVM Fits Into the Game Loop==<br />
<br />
Let's pretend that on a certain map in your game, a certain NPC is set up to "run script 4" when the user walks up to it and presses "Accept". Upon pressing "Accept", the game engine checks if this NPC has a script ID assigned to its "accept" behavior. The classic OHR performs this same task. If one exists, it is initialized and cached. At this point, the game engine continues with its tasks for that cycle (e.g., message boxes, vehicles). This (including user input) constitutes the "Update" phase of the game loop. Following this is the "Paint" phase, which shows one frame's worth of updates to the user. After painting, the tick count is incremented, and the loop repeats. The first thing that happens then is the "scripts" phase, during which scripts are actually executed (see below). The loop repeats indefinitely.<br />
<br />
[[Image:Hf_flowchart.png]]<br />
<br />
<br />
<br />
===Some Technical Considerations===<br />
<br />
When a script is triggered by any means, it suspends the currently-executing script; see "The Stack" in "[[How does plotscripting work?]]". This makes sense; if your script calls another script, one expects the new script to take over. It also means that if you step on several map tiles which each have (long) scripts, the last maptile's script will finish first. (Threading will probably change this.)<br />
<br />
But wait, there's more! Several small but important details of script initialization include:<br />
# If you trigger the same script twice (e.g., by stepping on two copies of an NPC one after the other) the script loader will detect that you are trying to load a script with the '''same id''' as the currently executing script. If the "Permit double-triggering of scripts" general bitset is on (or if the script is explicitly called by ''another script'') then this is allowed, and a second instance of the script is initialized. <br />
# When a script instance is '''first''' loaded, its "delay" counter is set to 2 ticks. This is ''not'' the same thing as instructing the script to "wait", because it remains the active script while delaying. If another script pre-empts this one, the delay counter does not decrement until the new script finishes. (When the new script finishes, the old one immediately resumes what it was doing, be it running or decrementing the "delay" counter.) <br />
# To prepare for parallel scripts, the active script is pushed to the top of the "waiting" stack, and is manipulated by a reference ID. This way, all scripts are kept in one place, and a future, parallel version of GAME_FMF can just scan through this list once each cycle, updating all scripts until they enter a waiting state.<br />
<br />
<br />
<br />
<br />
=Virtual Hardware Specification=<br />
<br />
The [http://en.wikipedia.org/wiki/Virtual_machine Wikipedia page] for Virtual Machines focuses on their use in emulating existing hardware. In fact, there is no hardware version of the HVM, just like there is no hardware equivalent of the Java Virtual Machine. Rather, Henceforth defines an unambiguous virtual hardware specification which can be ported to any platform with guaranteed interoperability. Currently, the only platform with a working HVM is the OHRRPGCEFMF, due to the bias of the HVM's design towards mobile devices.<br />
<br />
Anything in the world can be described by its nouns and verbs --the things it '''has''' and what they '''do'''. So let's inspect the '''pieces''' of the HVM and how they '''interact'''.<br />
<br />
<br />
==VM Data Structures==<br />
<br />
[[Image:Fmf_hvm_data_structures.png]]<br />
<br />
Green and red lines in this diagram indicate references from one item to another, although they are not drawn for every single reference (because that would be craaaaaaazy). <br />
<br />
The Script UDT block is particularly garish, because it is accommodating two different script types: user-defined scripts and named subroutines. Each component is colored differently depending on its use.<br />
<br />
<br />
=== What these do, top-down ===<br />
<br />
One way of learning how these data structures work is by discussing what each piece does on its own, and then assuming their combination. This is like studying one of those [http://www.applesource.com/peeler.html apple machines]; we say that this piece pierces the apple and holds it, that piece slices the skin off, and that blade cores the apple.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_top-down.png|link=FMF:The_HVM,_From_the_Top_Down]] || valign="top" | '''[[FMF:The_HVM,_From_the_Top_Down | Top-Down Description of the HVM]]'''<br>Read on for a detailed summary of the HVM's parts.<br />
|}<br />
<br />
<br />
<br />
===What These Do: Bottom-Up Description===<br />
<br />
If you've ever seen an [http://www.applesource.com/peeler.html apple machine] like the one just mentioned, you're probably aware that describing each element does absolutely nothing to help you understand how the thing works --you have to actually use it on an apple (preferably very slowly) to see how the magic is done. This is the essence of a bottom-up description: describe how certain parts '''interact''' at critical moments in the operation of the whole.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_bottom-up.png|link=FMF:The_HVM,_From_the_Bottom_Up]] || valign="top" | '''[[FMF:The_HVM,_From_the_Bottom_Up | Bottom-Up Description of the HVM]]'''<br>Read on for a detailed summary of how the HVM's parts interact.<br />
|}<br />
<br />
<br />
<br />
<br />
=HF Source Code & Formats=<br />
<br />
Strictly speaking, Henceforth has three different formats for its source code, and there is no such thing as "compiled Henceforth". These three formats are '''Format T''' (text-based), '''Format B''' (bytecode), and '''Format HF''' (compressed). Practically, most user code is generated in '''Format T''', which is then converted to '''Format B''' and compressed (to '''Format HF''') for storage. The HVM reads all three formats, but in doing so it converts T & HF to B. <br />
<br />
That said, we shall begin our study of Henceforth syntax by exhaustively reviewing all Henceforth commands in "source code" format ('''Format T'''), and then describe the mappings to other formats. A few commands cannot be explicitly represented in text format, as the following table details. We shall provide a recommended way of typing these when possible.<br />
<br />
{| cellspacing=0<br />
| &nbsp; || style="border: 1px solid black;" | '''Type''' || style="border: 1px solid black;" | '''Use'''<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format T || valign="top" style="border: 1px solid black;" | text || valign="top" style="border: 1px solid black;" | All human interaction (coding, discussing ideas, etc.) occurs in this format. Thins like script-local subroutines and function parameters cannot be expressed explicitly in this format.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format B || valign="top" style="border: 1px solid black;" | bytecode (binary) || valign="top" style="border: 1px solid black;" | The HVM executes Format B code. The HSP2HF cross-compiler outputs this format. Contains Henceforth Extensions for Scripting.<br />
|-<br />
| valign="top" style="border: 1px solid black;" | Format HF || valign="top" style="border: 1px solid black;" | compressed || valign="top" style="border: 1px solid black;" | The XRPG specification requires all Format B code to be compressed into format HF lumps, which are then stored locally on the user's phone.<br />
|}<br />
<br />
Henceforth source is a rather slim language. Numbers and strings are supported, but concatenations like "Hello"+" world" and decimals like 2.4 are not. A syntax exists for "do" and "if" statements, and for declaring functions and variables. Several smaller hacks exist for efficiency's sake, as described earlier. And that's it. Anything else is either a "primitive" like '''add''', or a "block label", subject to the polymorphic lookup that characterizes Forth-like languages. Henceforth is not based explicitly on Classic Forth, but it hopes to learn from the mistakes of this peculiar family of languages. One of the main disadvantages to ANSI Forth is its pandering to ANSI C fixations, overcomplicating a language which, at its heart, always championed simplicity and elegance.<br />
<br />
<br />
==Format B Bytecode Specification (HFB)==<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_format-b.png|link=FMF:HFB]] || valign="top" | '''[[FMF:HFB | Format B Specification]]'''<br>Format B code is detailed into exhaustion on a separate page.<br />
|}<br />
<br />
<br />
<br />
<br />
==Henceforth Syntax Dictionary==<br />
<br />
This section contains all of the commands that you could enter into a run-time interpreter (if such a one existed). There is no reference to HSpeak scripts, GAME API calls, or anything else; this section simply explains Henceforth as a language.<br />
<br />
Think of it this way: A Henceforth "script" (called a subroutine) is a slimmed-down version of a Henceforth HSpeak "script" (called a script). A script maintains a separate stack, for one thing, and script-local subroutines, for another. So, by way of example, calling "wait" within a subroutine would cause the HVM to crash.<br />
<br />
If you're not fully sick of the differences yet, consider this: A script cannot be specified in textual format (that's only a recommended part of the specification, not required) while a subroutine is easy to explain in textual format. We hope to clarify this section in the future. We'll get back to you.<br />
<br />
<br />
===Numbers===<br />
<br />
Numbers are represented by signed integers such as 2, 18, -35, or 43. When the HVM encounters a number, it pushes it to the stack. That's all for numbers, so let's cover stack effect diagrams briefly; they're necessary to understand more complicated features. Consider:<br />
{| border="1" cellpadding="3" rules="all"<br />
|-<br />
| <tt style="font-size:10pt">1 2 3</tt>|| <tt style="font-size:10pt">7</tt> || <tt style="font-size:10pt">1 2 3 7</tt><br />
|}<br />
<br />
'''''Note:''''' Stack-effect diagrams are oriented with the top of the stack on the right.<br />
This means that the stack starts with the elements 1, 2, and 3 already in it, is then given the command "7", and the resulting stack is now 1, 2, 3, then 7. These is an example of the ''prototype'' style of learning; to teach you how to use a command, I give you a real-world example of how it works. <br />
<br />
If I want to write a stack effect diagram without the fancy table, I might use parentheses and the "--" symbol to mean "the operator we're talking about". For example: "if the interpreter encounters -5, the resulting stack-effect diagram would be <tt style="font-size:10pt">(1 2 3 -- 1 2 3 -5)</tt>". <br />
<br />
And now we're ''way'' beyond numbers.<br />
<br />
<br />
===Primitives===<br />
<br />
When the HVM interprets a literal string like '''exp''', '''mycode''', or '''abs''', it searches for the relevant subroutine. The primitives are exceptions to this; they are always executed immediately. A primitive consists of lowercase letters and underscores, and must contain at least one letter. We call this combination its '''symbol name'''.<br />
<br />
Here is a list of primitives, and their stack-effect diagrams:<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Primitive''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="5"| Stack Manipulation || <tt style="font-size:10pt">dup</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 3 )</tt> || "Duplicate" the top of the stack.<br />
|-<br />
| <tt style="font-size:10pt">swap</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 3 2 )</tt> || "Swap" the top two elements of the stack.<br />
|-<br />
| <tt style="font-size:10pt">drop</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 )</tt> || "Drop" the top element off the stack.<br />
|-<br />
| <tt style="font-size:10pt">over</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 2 )</tt> || Pull the penultimate element "over" the top element.<br />
|-<br />
| <tt style="font-size:10pt">rot</tt> || <tt style="font-size:10pt">( 1 2 3 4 -- 1 4 3 2 )</tt> || "Rotate" the top three elements of the stack.<br />
|-<br />
| rowspan="5"| Arithmetic || <tt style="font-size:10pt">add</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 5 )</tt> || Addition<br />
|-<br />
| <tt style="font-size:10pt">sub</tt> || <tt style="font-size:10pt">( 7 3 2 -- 7 1 )</tt> || Subtraction<br />
|-<br />
| <tt style="font-size:10pt">mult</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 6 )</tt> || Multiplication<br />
|-<br />
| <tt style="font-size:10pt">div</tt> || <tt style="font-size:10pt">( 1 7 3 -- 1 2 )</tt> || Division<br />
|-<br />
| <tt style="font-size:10pt">random</tt> || <tt style="font-size:10pt">( 1 4 8 -- 1 6 )</tt> || Random Number<br />
|-<br />
| rowspan="4"| Bitwise Operators || <tt style="font-size:10pt">b_xor</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 17 )</tt> || Exclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_or</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 53 )</tt> || Inclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_and</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 36 )</tt> || Bitwise "And"<br />
|-<br />
| <tt style="font-size:10pt">b_not</tt> || <tt style="font-size:10pt">( 1 52 -- 1 4294967243 )</tt> || Bitwise "Not"<br />
|-<br />
| rowspan="6"| Logical Operators || <tt style="font-size:10pt">eq</tt> || <tt style="font-size:10pt">( 1 2 2 -- 1 1 )</tt> || Logical "Equals"<br />
|-<br />
| <tt style="font-size:10pt">lt</tt> || <tt style="font-size:10pt">( 1 5 2 -- 1 0 )</tt> || Logical "Less Than"<br />
|-<br />
| <tt style="font-size:10pt">not</tt> || <tt style="font-size:10pt">( 1 5 1 -- 1 5 0 )</tt> || Logical "Not"<br />
|-<br />
| <tt style="font-size:10pt">and</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 1 )</tt> || Logical "And"<br />
|-<br />
| <tt style="font-size:10pt">or</tt> || <tt style="font-size:10pt">( 1 5 0 1 -- 1 5 1 )</tt> || Inclusive Logical "Or"<br />
|-<br />
| <tt style="font-size:10pt">xor</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 0 )</tt> || Exclusive Logical "Or"<br />
|}<br />
<br />
There is no boolean type; the number zero is considered "false", and any other number is considered "true". For clarity, the number "1" is the preferred way to represent "true".<br />
<br />
The primitives "and" and "or" do ''not'' short-circuit. This is because a short-circuiting operator behaves ambiguously for stack-based languages, and achieves no gain in performance or security (since it operates on values which have already been computed.) The [[HSP2HF || Cross-Compiler]] has an example of how to perform short-circuiting calculations in a manual way; see the section on Mathematical Functions.<br />
<br />
A number of additional keywords such as '''break''' and '''continue''' will be discussed in the section on If statements and Loops.<br />
<br />
===Blocks (Subroutines)===<br />
<br />
Labeled blocks, also called subroutines, are the core of Henceforth's modularization. They are strikingly similar to primitives, except that they can be defined, undefined, and overloaded. Labels contain letters and underscores, just like primitives.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This creates a function called "mod", which performs a series of stack manipulations and basic arithmetic.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;7 3 mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Traditionally, we push the "parameters" to the subroutine in increasing order, and expect it to remove these arguments (and replace them with a return value, if any) during the course of its operation.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Overriding a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;drop drop 0<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;After executing this snippet, "mod" will have two definitions. Calling "mod" will result in the latter being evoked, which simply clears the parameters and returns zero.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Un-defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">/NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;/mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;If we execute the code from "Overriding a Subroutine", and then execute this code, "mod" will be left with a single definition: the first one.<br />
|}<br />
<br />
<br />
Any labeled block remains in effect even if the script which defined it finishes execution; make sure to un-define any subroutines you do not wish to linger. The HVM capitalizes on this behavior to limit the number of built-in primitives. The modulus operator described earlier is an example, as is "less than or equal to", which can be defined compositely. Here is a list of all the subroutines the HVM loads by default, with their stack-effect diagrams.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Subroutine''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="2"| Arithmetic || <tt style="font-size:10pt">exp</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 8 )</tt> || Compute x raised to the y ("exponent")<br />
|-<br />
| <tt style="font-size:10pt">mod</tt> || <tt style="font-size:10pt">( 1 10 4 -- 1 2 )</tt> || Calculate the "modulus"<br />
|-<br />
| rowspan="4"| Logical Operators || <tt style="font-size:10pt">neq</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 1 )</tt> || Logical "Not Equal To"<br />
|-<br />
| <tt style="font-size:10pt">lte</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 1 )</tt> || Logical "Less Than or Equal To"<br />
|-<br />
| <tt style="font-size:10pt">gt</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 0 )</tt> || Logical "Greater Than"<br />
|-<br />
| <tt style="font-size:10pt">gte</tt> || <tt style="font-size:10pt">( 1 63 45 -- 1 1 )</tt> || Logical "Greater Than or Equal To"<br />
|-<br />
| rowspan="2"| Convenience Methods || <tt style="font-size:10pt">true</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 45 52 1 )</tt> || Locus-of-control for "true"<br />
|-<br />
| <tt style="font-size:10pt">false</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 45 45 0 )</tt> || Locus-of-control for "false"<br />
|}<br />
<br />
<br />
===Variables===<br />
<br />
Shuffling the stack beyond the fourth element can get messy. As such, subroutines can allocate ''local variables'' which stay in scope as long as that script is running. Note that variables follow a similar naming convention to subroutines. They are accessed using the @VAR and VAR@ commands. You can remember them easily by putting an imaginary "stack" next to them: <br />
* '''(stack)@VAR''' takes a value from the stack and throws it '''at''' VAR. This is the same as setting VAR = pop().<br />
* '''VAR@(stack)''' takes the value in VAR and throws it '''at''' the stack. This is the same as pushing VAR.<br />
<br />
Here is some sample source for the '''lte''' function, written using variables. Note that, in reality, '''lte''' only uses the stack. This is much more efficient for the HVM, which only initializes the local variable store if it detects your script trying to access it.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using Variables'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\lte{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Pop the stack twice to get our local variables.<br>&nbsp;&nbsp;&nbsp;&nbsp;#We assume that we are testing @lhs <= @rhs<br>&nbsp;&nbsp;&nbsp;&nbsp;@rhs&nbsp;&nbsp;@lhs<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the first test (<) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res1<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;lt&nbsp;&nbsp;@res1<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the second test (==) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res2<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;eq&nbsp;&nbsp;@res2<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our two tests and do an or test. Leave it on<br>&nbsp;&nbsp;&nbsp;&nbsp;# the stack to be returned<br>&nbsp;&nbsp;&nbsp;&nbsp;res1@&nbsp;&nbsp;res2@&nbsp;&nbsp;or<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;As an exercise, you should try to convert this to a stack-based alternative. It should end up more concise, and also faster.<br />
|}<br />
<br />
<br />
===Do/If & Loops===<br />
<br />
Most Forth-like languages have an "if" statement, and Henceforth is no exception. However, rather than encapsulating loops into labeled blocks (like Classic Forth), it adds another keyword: "do". This serves two purposes: first, it allows for better inlining than subroutines, and second, it provides a locus of control for jump commands. The second rationale is sometimes written as "otherwise, we'd have to use '''goto'''".<br />
<br />
Inside of a do loop, the commands '''break''' and '''continue''' also take effect; you should read [[HSZ#Id_11:_break | the HSZ documentation]] for an example of how these work conceptually. The mechanism for '''break''' and '''continue''' is different in the HVM, but the effect is the same.<br />
<br />
The standard Hamsterspeak engine also allows breaking out of multiple loops at once. In order to keep the syntax clean in Henceforth, we introduce two bytecodes for that purpose: '''break_x''' and '''continue_x'''. These take the element from the top of the stack, and break out of that many loops. The '''break''' command is effectively equivalent to "1 break_x". <br />
<br />
Thus, we add five more keywords to the list of Henceforth primitives: '''do''', '''if''', '''else''', '''break''', and '''continue'''. Brackets are used to denote code blocks where relevant. We will cover the syntax in simple examples. The first is a user-defined subroutine, "divide_safe", which avoids dividing by zero, and the second is an example of the Fibonacci sequence using "do" to achieve the effect of a "while" loop.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Save Divide-by-Zero Handling'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\divide_safe{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Do a zero-check on the divisor<br>&nbsp;&nbsp;&nbsp;&nbsp;dup&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;#Now, branch on that<br>&nbsp;&nbsp;&nbsp;&nbsp;if{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pop&nbsp;&nbsp;pop&nbsp;&nbsp;0<br>&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;div<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Note that returning zero is just one way of handling the erroneous case. Also, take note of the two "pop" commands; these are required!<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Fibonacci Numbers'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\fibonacci{<br>&nbsp;&nbsp;&nbsp;&nbsp;@count&nbsp;&nbsp;&nbsp;&nbsp; #Must be >=1<br>&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;@fprev&nbsp;&nbsp;#fib(n-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;@fnow&nbsp;&nbsp; #fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Test-and-decrement<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;-1&nbsp;&nbsp;add&nbsp;&nbsp;@count<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Iterate & Accumulate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprev@&nbsp;&nbsp;fnow@&nbsp;&nbsp; add<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fnow@&nbsp;&nbsp; @fprev<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fnow<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Re-do the loop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fnow@<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This subroutine fails given zero as input. Modify it by adding a simple if statement before the main loop. <br />
|}<br />
<br />
This second example is far more interesting; it answers the question "How do I Loop in Henceforth?"<br />
<br />
'''''A Note About Recursion''''': Although Henceforth fully supports recursion, the HVM is not designed for heavily-recursive functions. When possible, please use an iterative solution.<br />
<br />
<br />
<br />
==Henceforth Extensions for Scripting==<br />
<br />
The Henceforth specification recommends (read: ''does not require'') that the following pseudo-syntax be adopted to describe the commands which appear only in the '''Format B''' code for HSpeak script equivalents. A vanilla HVM should recognize these tokens and either ignore them, simulate them, or generate an informative error message when it encounters them.<br />
<br />
Backwards compatibility need not be preserved when converting between the '''Format B''' and '''Format T''' equivalents for script extensions.<br />
<br />
<br />
<br />
===HSpeak API Calls & User Scripts===<br />
<br />
The HVM has knowledge of most "built-in" functions that Hamsterspeak provides. These are called like any other subroutine, with one exception: most format converters create specialized subroutine calls out of them. In other words, if I write a call to '''wait_npc''' in my ('''Format T''') code, the converter will create ('''Format B''') source that points ''explicitly'' to the definition of '''wait_npc''' in the HVM; it is '''''not''''' possible to overload '''wait_npc'''. This breaks our promise of fully-interchangeable source formats, but it is hardly noticeable to most users, and keeps our global function lookup table from becoming too cluttered.<br />
<br />
See [[FMF:Plotdict]] for a listing of all API calls and their syntax in Henceforth.<br />
<br />
User-defined scripts are treated exactly like API calls; they are assigned IDs and called explicitly. <br />
<br />
<br />
===Strings===<br />
<br />
Strings in Classic Forth are handled by simply pushing a series of integers (for each letter) to the stack. Eventually, Henceforth may go this route, but for now the API calls to strings are so simple that we chose a much simpler implementation:<br />
* Strings are a series of letters between double-quotes, like "Hello World", "Going?", or "Unicode: \u1000". As hinted by the last example, the HVM supports Unicode.<br />
* When a string is encountered (usually at the top of a script) it is pushed to the '''String Table''' for that script. Strings are pushed in order, cannot be removed or easily manipulated, and have IDs in increasing order.<br />
<br />
This is an ''ad hoc'' implementation of strings, until we get a clearer picture where the standard OHR's development is going with these ideas.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using a String'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;"Hello world!"<br>&nbsp;&nbsp;0 show_str</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Defining "Hello world!" will place this string at index zero; pushing 0 and then calling the API function '''show_str''' will reference that string explicitly.<br />
|}<br />
<br />
<br />
===Script-Local Subroutines===<br />
<br />
Here's where Henceforth starts to really get picky. When a Hamsterspeak script is converted to Henceforth, it is basically converted into a series of labeled blocks that call each other. Recalling that user-level "scripts" are effectively subroutines, the keen observer will note that a lot of resources are wasted supporting "subroutines" that are, actually, just glorified "goto" blocks. For example, a definition such as "\plus_two{ 2 add }" doesn't need its own stack space --it's just a snippet of code! <br />
<br />
With that in mind, subroutines are allowed to define "script-local" subroutines which operate along a single thread of control within a named block. These are defined with a given ID, and called with an array-like syntax. They cannot be un-defined or overloaded, although theoretically they could be replaced (by re-defining them).<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\[ID]{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\[5]{<br>&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;plus<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Our "plus two" example from before, assigned to index 5.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">[ID]()</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;16&nbsp;&nbsp;[5]()</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This pushes 16, and calls local block 5. The result is a stack containing the number 18, if we define [5] as listed above. ''The parentheses, although not technically necessary, do a great deal to visually distinguish script-local subroutines from function parameters.''<br />
|}<br />
<br />
<br />
===Local Parameters & Return Values===<br />
<br />
'''''MAKE A NOTE: Give an example of how my_script{ exp } calls "exp" ON the stack of my_script. '''''<br />
<br />
An astute reader will have noticed that scripts currently have no way of passing values to other scripts. Calling "5 6 my_func" won't work, because '''my_func''' will get its own independent stack. This problem is circumvented through the use of local parameters, which also provide a slight speedup for small, frequently called subroutines. The local parameters are accessed by ID, with -1 a special ID that refers to the script's return value. <br />
* Executing @[5] will "pop the (parent) stack to local parameter 5"<br />
* Executing [5]@ will "push local parameter 5 onto the (current) stack<br />
For the return value, it's backwards:<br />
* Executing @[-1] will "pop the (current) stack to the return value"<br />
* Executing [-1]@ will "push the return value onto the (parent) stack", '''''unless''''' the return value is set to ''invalid'' (INT_MIN, for now).<br />
The only point of interest is that last bit. The problem is, the HSpeak command "return" sets the return value, and "exitscript" will exit and return that value. Some scripts simply aren't intended to use their return values, which would leave an extra element on the stack. We could always just pop the return value, but we don't want to punish a highly modularized script with an un-necessary drag on performance. This is the alternative we came up with.<br />
<br />
===Comments===<br />
<br />
Henceforth supports comments, although it's important to realize that Formats B and HF will drop your comments; they can't be encoded. A comment begins with a hash mark (#) and ends with a line feed. <br />
<br />
<br />
===Global Variables===<br />
<br />
All that's left are global variables, which are manipulated with syntax like '''@[65.G]''' and '''[65.G]@'''. We realize this is a bit awkward, and we're open to a cleaner syntax (once we actually get a release going).<br />
<br />
= The Cross-Compiler =<br />
<br />
[[HSP2HF]]<br />
<br />
=Final Considerations=<br />
<br />
(later)<br />
<br />
<br />
<br />
<br />
<br />
<br />
{{Incomplete}}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=File:Hvm_thumb_format-b.png&diff=18619
File:Hvm thumb format-b.png
2009-01-15T07:29:06Z
<p>Sorlok reaves: /* Thumbnail for the link to the format B page of the HVM. */</p>
<hr />
<div>/* Thumbnail for the link to the format B page of the HVM. */</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Henceforth_VM&diff=18618
Henceforth VM
2009-01-15T07:21:36Z
<p>Sorlok reaves: /* Added a nicer-style link to the other pages. */</p>
<hr />
<div>{{FMF}}<br />
<br />
The Henceforth Virtual Machine (HVM, Henceforth VM) is used to dynamically interpret Henceforth (HF, Hamsterforth) scripts. The OHRRPGCE FMF does not use Hamsterspeak's traditional HSX/HSZ files; rather, it opts for a stack-based alternative. Henceforth files are stored with the .HF extension.<br />
<br />
<br />
<div style="border: 2px red solid; width: 600px; padding: 10px"><span style="color:#AA0000; font-weight:bold; font-size: 20pt">Warning: </span> This document is complete, except for the "final considerations" section, which will be trivial to add later.<br>We are currently in the process of reviewing the entire design specification, writing a toy interpreter, and polishing up (and segmenting) this document. <br>We'll gladly take your suggestions on the "Discussion" page, but please do not modify, distribute, or trust this document. It will soon be as open to the community as everything else on this Wiki, but it'll take a few weeks to do the final review.<br><br>Thanks~<br>/Seth<br />
</div><br />
<br />
<br />
=Acknowledgements=<br />
<br />
A big thanks to both James and Ralph for their well-founded suggestions regarding the HVM, and to Mike for clarifying countless issues. If you start reading into the minute details of the HVM, you'll find that a whole lot of it is lean, clean, and sensible. This is almost entirely due to the suggestions and experience of the OHR's patriarchs, which I truly appreciate. <br />
<br />
<br />
=Introduction to Henceforth=<br />
<br />
Henceforth is a simple, stack-based programming language which the OHRRPGCE FMF uses as a crossover language from traditional Hamsterspeak. Like all of the lumps exclusive to this project, .[[HF]] lumps containing Henceforth code are cross-compiled automatically for you when you convert your game to XRPG Format. However, all developers should consider learning basic Henceforth syntax to aid them in writing run-time debug scripts and compile-time optimized versions of their most time-critical Hamsterspeak scripts. <br />
<br />
Henceforth is a stack-based language, designed to obviate register allocation problems and minimize script size. You should read James's [[How_does_plotscripting_work%3F#The_Stack | excellent analogy of a stack]], if you aren't familiar with the concept. Hamsterspeak uses the stack for ordering scripts; Henceforth, however, uses the stack for storing data.<br />
<br />
<br />
==A Henceforth Primer==<br />
<br />
Although Henceforth is not a compiled language, you can pretend that "source" Henceforth is compiled into "binary" HFZ lumps, which the game engine runs, and only [[User:Sorlok_reaves | one person]] will cringe and correct you. So let's have a look at some Henceforth source code, shall we? Any text after a [http://dictionary.reference.com/browse/hash%20mark hash mark] until the next newline is considered a comment, and [http://dictionary.reference.com/browse/whitespace whitespace] is used to separate commands. Consider the following script.<br />
#One way to separate commands is by hitting "Enter" after each command.<br />
4 #Push 4<br />
5 #Push 5<br />
add #Pop the top two elements and add them. Push the result.<br />
@result #Pop the top element and store it in the variable "result".<br />
<br />
#Another way to separate commands is using spaces.<br />
4 5 add @result<br />
Most programmers will use a mix of spaces and newlines to keep their scripts clear and sensible. For example, "4 5 add" should probably always be on the same line. Regardless of what format you use, manipulation of the stack is the same. The following diagram shows how the stack (and the variables list) changes as the HVM reads each command, from left to right.<br />
<br />
[[Image:Fmf_stack_diagram.png]]<br />
<br />
<br />
==The "Pure" Virtual Machine==<br />
<br />
From a theoretical point of view, Henceforth does not require a location to store variables. Rather, we could simply add '''block labels''' and a delayed execution ('''prepending''') command:<br />
* Surrounding some source with '''\name{}''' will label that block with "name"; if '''name''' later appears as a command, this block will be inserted in-place. Later writing '''/name''' will un-define "name". If multiple blocks are labeled similarly, the most-recent label is considered valid. <br />
* Pre-pending "&" to a command will push the result of that command ''into the source code itself'', at the time that command is first encountered. <br />
<br />
'''Block labels''' allow functions to be written very naturally:<br />
#The "modulo" function expects two arguments to be<br />
# on the stack; it transforms (X Y) to (X%Y)<br />
\modulo {<br />
swap dup rot dup rot swap #Now we have (X Y X Y)<br />
div mult sub #The heart of our algorithm<br />
}<br />
There's no need to "return" anything; the labeled block operates in a way that leaves one item on the stack. Here's some code to use this block as a function:<br />
#We want to compute 7%3<br />
7 3 modulo #After this, "1" is left on the stack<br />
<br />
This is, in fact, the exact syntax for writing a function in Henceforth. Now, let's cover '''prepending''':<br />
#Avoiding variables with named blocks.<br />
4 5 add <br />
\result{ #Label the start of the "result" block<br />
&pop #This is tricky: the first time we encounter this<br />
# (i.e., when "\result" is defined), it executes "pop",<br />
# resulting in "9", which is pushed into the source.<br />
} #Closes the "\result" label.<br />
<br />
result #Calling "result" will now push "9".<br />
<br />
Unlike named blocks, prepending is not actually implemented in Henceforth; you can ignore that last bit of code if you don't understand it. Which brings us to our main point:<br />
<br />
<big><big style="color:green;"><br />
'''Henceforth was designed around a "pure" VM'''</big><big>, but it was then modified to </big><big style="color:green;">'''run efficiently'''</big><big> on a phone, and to </big><big style="color:green;">'''allow an easy mapping'''</big><big> from existing Hamsterspeak scripts. <br />
</big></big><br />
<br />
It is crucial that you keep the ideal virtual machine in mind, or else you will start asking about "Henceforth's registers" (which don't exist) or you'll start using variables way too much. You also need to be cognizant of the dirty details required to kludge Hamsterspeak onto a phone; otherwise, things like script-level subroutines and our extravagant caching mechanism will seem redundant and senseless.<br />
<br />
(Note to readers: At this point, if you're only interested in the Henceforth language, you can skip down to the section on [[Henceforth_VM#Henceforth Syntax Dictionary | Henceforth Syntax]].)<br />
<br />
<br />
==Where the HVM Fits Into the Game Loop==<br />
<br />
Let's pretend that on a certain map in your game, a certain NPC is set up to "run script 4" when the user walks up to it and presses "Accept". Upon pressing "Accept", the game engine checks if this NPC has a script ID assigned to its "accept" behavior. The classic OHR performs this same task. If one exists, it is initialized and cached. At this point, the game engine continues with its tasks for that cycle (e.g., message boxes, vehicles). This (including user input) constitutes the "Update" phase of the game loop. Following this is the "Paint" phase, which shows one frame's worth of updates to the user. After painting, the tick count is incremented, and the loop repeats. The first thing that happens then is the "scripts" phase, during which scripts are actually executed (see below). The loop repeats indefinitely.<br />
<br />
[[Image:Hf_flowchart.png]]<br />
<br />
<br />
<br />
=Virtual Hardware Specification=<br />
<br />
The [http://en.wikipedia.org/wiki/Virtual_machine Wikipedia page] for Virtual Machines focuses on their use in emulating existing hardware. In fact, there is no hardware version of the HVM, just like there is no hardware equivalent of the Java Virtual Machine. Rather, Henceforth defines an unambiguous virtual hardware specification which can be ported to any platform with guaranteed interoperability. Currently, the only platform with a working HVM is the OHRRPGCEFMF, due to the bias of the HVM's design towards mobile devices.<br />
<br />
Anything in the world can be described by its nouns and verbs --the things it '''has''' and what they '''do'''. So let's inspect the '''pieces''' of the HVM and how they '''interact'''.<br />
<br />
<br />
==VM Data Structures==<br />
<br />
[[Image:Fmf_hvm_data_structures.png]]<br />
<br />
Green and red lines in this diagram indicate references from one item to another, although they are not drawn for every single reference (because that would be craaaaaaazy). <br />
<br />
The Script UDT block is particularly garish, because it is accommodating two different script types: user-defined scripts and named subroutines. Each component is colored differently depending on its use.<br />
<br />
<br />
=== What these do, top-down ===<br />
<br />
One way of learning how these data structures work is by discussing what each piece does on its own, and then assuming their combination. This is like studying one of those [http://www.applesource.com/peeler.html apple machines]; we say that this piece pierces the apple and holds it, that piece slices the skin off, and that blade cores the apple.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_top-down.png|link=FMF:The_HVM,_From_the_Top_Down]] || valign="top" | '''[[FMF:The_HVM,_From_the_Top_Down | Top-Down Description of the HVM]]'''<br>Read on for a detailed summary of the HVM's parts.<br />
|}<br />
<br />
<br />
<br />
===What These Do: Bottom-Up Description===<br />
<br />
If you've ever seen an [http://www.applesource.com/peeler.html apple machine] like the one just mentioned, you're probably aware that describing each element does absolutely nothing to help you understand how the thing works --you have to actually use it on an apple (preferably very slowly) to see how the magic is done. This is a bottom-up description: describe how certain parts '''interact''' at critical moments in the operation of the whole.<br />
<br />
{| style="border: 2px dashed black;" cellpadding="3" rules="all"<br />
| valign="top" style="padding:0px;" | [[File:Hvm_thumb_bottom-up.png|link=FMF:The_HVM,_From_the_Bottom_Up]] || valign="top" | '''[[FMF:The_HVM,_From_the_Bottom_Up | Bottom-Up Description of the HVM]]'''<br>Read on for a detailed summary of how the HVM's parts interact.<br />
|}<br />
<br />
<br />
<br />
==Some Technical Considerations==<br />
<br />
When a script is triggered by any means, it suspends the currently-executing script; see "The Stack" in "[[How does plotscripting work?]]". This makes sense; if your script calls another script, one expects the new script to take over. It also means that if you step on several map tiles which each have (long) scripts, the last maptile's script will finish first. (Threading will probably change this.)<br />
<br />
But wait, there's more! Several small but important details of script initialization include:<br />
# If you trigger the same script twice (e.g., by stepping on two copies of an NPC one after the other) the script loader will detect that you are trying to load a script with the '''same id''' as the currently executing script. If the "Permit double-triggering of scripts" general bitset is on (or if the script is explicitly called by ''another script'') then this is allowed, and a second instance of the script is initialized. <br />
# When a script instance is '''first''' loaded, its "delay" counter is set to 2 ticks. This is ''not'' the same thing as instructing the script to "wait", because it remains the active script while delaying. If another script pre-empts this one, the delay counter does not decrement until the new script finishes. (When the new script finishes, the old one immediately resumes what it was doing, be it running or decrementing the "delay" counter.) <br />
# To prepare for parallel scripts, the active script is pushed to the top of the "waiting" stack, and is manipulated by a reference ID. This way, all scripts are kept in one place, and GAME_FMF can just scan through this list once each cycle, updating all scripts until they enter a waiting state.<br />
<br />
=HF Source Code & Formats=<br />
<br />
Strictly speaking, Henceforth has three different formats for its source code, and there is no such thing as "compiled Henceforth". These three formats are '''Format T''' (text-based), '''Format B''' (bytecode), and '''Format HF''' (compressed). Practically, most user code is generated in '''Format T''', which is then converted to '''Format B''' and compressed (to '''Format HF''') for storage. The HVM reads all three formats, but in doing so it converts T & HF to B. <br />
<br />
That said, we shall begin our study of Henceforth syntax by exhaustively reviewing all Henceforth commands in "source code" format ('''Format T'''), and then describe the mappings to other formats. <br />
<br />
Henceforth source is a rather slim language. Numbers and strings are supported, but concatenations like "Hello"+" world" and decimals like 2.4 are not. A syntax exists for "do" and "if" statements, and for declaring functions and variables. Several smaller hacks exist for efficiency's sake, as described earlier. And that's it. Anything else is either a "primitive" like '''add''', or a "block label", subject to the polymorphic lookup that characterizes Forth-like languages. Henceforth is not based explicitly on Classic Forth, but it hopes to learn from the mistakes of this peculiar family of languages. One of the main disadvantages to ANSI Forth is its pandering to ANSI C fixations, overcomplicating a language which, at its heart, always championed simplicity and elegance.<br />
<br />
<br />
==Henceforth Syntax Dictionary==<br />
<br />
This section contains all of the commands that you could enter into a run-time interpreter (if such a one existed). There is no reference to HSpeak scripts, GAME API calls, or anything else; this section simply explains Henceforth as a language.<br />
<br />
Think of it this way: A Henceforth "script" (called a subroutine) is a slimmed-down version of a Henceforth HSpeak "script" (called a script). A script maintains a separate stack, for one thing, and script-local subroutines, for another. So, by way of example, calling "wait" within a subroutine would cause the HVM to crash.<br />
<br />
If you're not fully sick of the differences yet, consider this: A script cannot be specified in textual format (that's only a recommended part of the specification, not required) while a subroutine is easy to explain in textual format. We hope to clarify this section in the future. We'll get back to you.<br />
<br />
<br />
===Numbers===<br />
<br />
Numbers are represented by signed integers such as 2, 18, -35, or 43. When the HVM encounters a number, it pushes it to the stack. That's all for numbers, so let's cover stack effect diagrams briefly; they're necessary to understand more complicated features. Consider:<br />
{| border="1" cellpadding="3" rules="all"<br />
|-<br />
| <tt style="font-size:10pt">1 2 3</tt>|| <tt style="font-size:10pt">7</tt> || <tt style="font-size:10pt">1 2 3 7</tt><br />
|}<br />
<br />
'''''Note:''''' Stack-effect diagrams are oriented with the top of the stack on the right.<br />
This means that the stack starts with the elements 1, 2, and 3 already in it, is then given the command "7", and the resulting stack is now 1, 2, 3, then 7. These is an example of the ''prototype'' style of learning; to teach you how to use a command, I give you a real-world example of how it works. <br />
<br />
If I want to write a stack effect diagram without the fancy table, I might use parentheses and the "--" symbol to mean "the operator we're talking about". For example: "if the interpreter encounters -5, the resulting stack-effect diagram would be <tt style="font-size:10pt">(1 2 3 -- 1 2 3 -5)</tt>". <br />
<br />
And now we're ''way'' beyond numbers.<br />
<br />
<br />
===Primitives===<br />
<br />
When the HVM interprets a literal string like '''exp''', '''mycode''', or '''abs''', it searches for the relevant subroutine. The primitives are exceptions to this; they are always executed immediately. A primitive consists of lowercase letters and underscores, and must contain at least one letter. We call this combination its '''symbol name'''.<br />
<br />
Here is a list of primitives, and their stack-effect diagrams:<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Primitive''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="5"| Stack Manipulation || <tt style="font-size:10pt">dup</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 3 )</tt> || "Duplicate" the top of the stack.<br />
|-<br />
| <tt style="font-size:10pt">swap</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 3 2 )</tt> || "Swap" the top two elements of the stack.<br />
|-<br />
| <tt style="font-size:10pt">drop</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 )</tt> || "Drop" the top element off the stack.<br />
|-<br />
| <tt style="font-size:10pt">over</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 2 )</tt> || Pull the penultimate element "over" the top element.<br />
|-<br />
| <tt style="font-size:10pt">rot</tt> || <tt style="font-size:10pt">( 1 2 3 4 -- 1 4 3 2 )</tt> || "Rotate" the top three elements of the stack.<br />
|-<br />
| rowspan="5"| Arithmetic || <tt style="font-size:10pt">add</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 5 )</tt> || Addition<br />
|-<br />
| <tt style="font-size:10pt">sub</tt> || <tt style="font-size:10pt">( 7 3 2 -- 7 1 )</tt> || Subtraction<br />
|-<br />
| <tt style="font-size:10pt">mult</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 6 )</tt> || Multiplication<br />
|-<br />
| <tt style="font-size:10pt">div</tt> || <tt style="font-size:10pt">( 1 7 3 -- 1 2 )</tt> || Division<br />
|-<br />
| <tt style="font-size:10pt">random</tt> || <tt style="font-size:10pt">( 1 4 8 -- 1 6 )</tt> || Random Number<br />
|-<br />
| rowspan="4"| Bitwise Operators || <tt style="font-size:10pt">b_xor</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 17 )</tt> || Exclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_or</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 53 )</tt> || Inclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_and</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 36 )</tt> || Bitwise "And"<br />
|-<br />
| <tt style="font-size:10pt">b_not</tt> || <tt style="font-size:10pt">( 1 52 -- 1 4294967243 )</tt> || Bitwise "Not"<br />
|-<br />
| rowspan="6"| Logical Operators || <tt style="font-size:10pt">eq</tt> || <tt style="font-size:10pt">( 1 2 2 -- 1 1 )</tt> || Logical "Equals"<br />
|-<br />
| <tt style="font-size:10pt">lt</tt> || <tt style="font-size:10pt">( 1 5 2 -- 1 0 )</tt> || Logical "Less Than"<br />
|-<br />
| <tt style="font-size:10pt">not</tt> || <tt style="font-size:10pt">( 1 5 1 -- 1 5 0 )</tt> || Logical "Not"<br />
|-<br />
| <tt style="font-size:10pt">and</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 1 )</tt> || Logical "And"<br />
|-<br />
| <tt style="font-size:10pt">or</tt> || <tt style="font-size:10pt">( 1 5 0 1 -- 1 5 1 )</tt> || Inclusive Logical "Or"<br />
|-<br />
| <tt style="font-size:10pt">xor</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 0 )</tt> || Exclusive Logical "Or"<br />
|}<br />
<br />
There is no boolean type; the number zero is considered "false", and any other number is considered "true". For clarity, the number "1" is the preferred way to represent "true".<br />
<br />
The primitives "and" and "or" do ''not'' short-circuit. This is because a short-circuiting operator behaves ambiguously for stack-based languages, and achieves no gain in performance or security (since it operates on values which have already been computed.) The [[HSP2HF || Cross-Compiler]] has an example of how to perform short-circuiting calculations in a manual way; see the section on Mathematical Functions.<br />
<br />
A number of additional keywords such as '''break''' and '''continue''' will be discussed in the section on If statements and Loops.<br />
<br />
===Blocks (Subroutines)===<br />
<br />
Labeled blocks, also called subroutines, are the core of Henceforth's modularization. They are strikingly similar to primitives, except that they can be defined, undefined, and overloaded. Labels contain letters and underscores, just like primitives.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This creates a function called "mod", which performs a series of stack manipulations and basic arithmetic.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;7 3 mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Traditionally, we push the "parameters" to the subroutine in increasing order, and expect it to remove these arguments (and replace them with a return value, if any) during the course of its operation.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Overriding a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;drop drop 0<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;After executing this snippet, "mod" will have two definitions. Calling "mod" will result in the latter being evoked, which simply clears the parameters and returns zero.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Un-defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">/NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;/mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;If we execute the code from "Overriding a Subroutine", and then execute this code, "mod" will be left with a single definition: the first one.<br />
|}<br />
<br />
<br />
Any labeled block remains in effect even if the script which defined it finishes execution; make sure to un-define any subroutines you do not wish to linger. The HVM capitalizes on this behavior to limit the number of built-in primitives. The modulus operator described earlier is an example, as is "less than or equal to", which can be defined compositely. Here is a list of all the subroutines the HVM loads by default, with their stack-effect diagrams.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Subroutine''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="2"| Arithmetic || <tt style="font-size:10pt">exp</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 8 )</tt> || Compute x raised to the y ("exponent")<br />
|-<br />
| <tt style="font-size:10pt">mod</tt> || <tt style="font-size:10pt">( 1 10 4 -- 1 2 )</tt> || Calculate the "modulus"<br />
|-<br />
| rowspan="4"| Logical Operators || <tt style="font-size:10pt">neq</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 1 )</tt> || Logical "Not Equal To"<br />
|-<br />
| <tt style="font-size:10pt">lte</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 1 )</tt> || Logical "Less Than or Equal To"<br />
|-<br />
| <tt style="font-size:10pt">gt</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 0 )</tt> || Logical "Greater Than"<br />
|-<br />
| <tt style="font-size:10pt">gte</tt> || <tt style="font-size:10pt">( 1 63 45 -- 1 1 )</tt> || Logical "Greater Than or Equal To"<br />
|-<br />
| rowspan="2"| Convenience Methods || <tt style="font-size:10pt">true</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 45 52 1 )</tt> || Locus-of-control for "true"<br />
|-<br />
| <tt style="font-size:10pt">false</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 45 45 0 )</tt> || Locus-of-control for "false"<br />
|}<br />
<br />
<br />
===Variables===<br />
<br />
Shuffling the stack beyond the fourth element can get messy. As such, subroutines can allocate ''local variables'' which stay in scope as long as that script is running. Note that variables follow a similar naming convention to subroutines. They are accessed using the @VAR and VAR@ commands. You can remember them easily by putting an imaginary "stack" next to them: <br />
* '''(stack)@VAR''' takes a value from the stack and throws it '''at''' VAR. This is the same as setting VAR = pop().<br />
* '''VAR@(stack)''' takes the value in VAR and throws it '''at''' the stack. This is the same as pushing VAR.<br />
<br />
Here is some sample source for the '''lte''' function, written using variables. Note that, in reality, '''lte''' only uses the stack. This is much more efficient for the HVM, which only initializes the local variable store if it detects your script trying to access it.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using Variables'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\lte{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Pop the stack twice to get our local variables.<br>&nbsp;&nbsp;&nbsp;&nbsp;#We assume that we are testing @lhs <= @rhs<br>&nbsp;&nbsp;&nbsp;&nbsp;@rhs&nbsp;&nbsp;@lhs<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the first test (<) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res1<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;lt&nbsp;&nbsp;@res1<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the second test (==) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res2<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;eq&nbsp;&nbsp;@res2<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our two tests and do an or test. Leave it on<br>&nbsp;&nbsp;&nbsp;&nbsp;# the stack to be returned<br>&nbsp;&nbsp;&nbsp;&nbsp;res1@&nbsp;&nbsp;res2@&nbsp;&nbsp;or<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;As an exercise, you should try to convert this to a stack-based alternative. It should end up more concise, and also faster.<br />
|}<br />
<br />
<br />
===Do/If & Loops===<br />
<br />
Most Forth-like languages have an "if" statement, and Henceforth is no exception. However, rather than encapsulating loops into labeled blocks (like Classic Forth), it adds another keyword: "do". This serves two purposes: first, it allows for better inlining than subroutines, and second, it provides a locus of control for jump commands. The second rationale is sometimes written as "otherwise, we'd have to use '''goto'''".<br />
<br />
Inside of a do loop, the commands '''break''' and '''continue''' also take effect; you should read [[HSZ#Id_11:_break | the HSZ documentation]] for an example of how these work conceptually. The mechanism for '''break''' and '''continue''' is different in the HVM, but the effect is the same.<br />
<br />
The standard Hamsterspeak engine also allows breaking out of multiple loops at once. In order to keep the syntax clean in Henceforth, we introduce two bytecodes for that purpose: '''break_x''' and '''continue_x'''. These take the element from the top of the stack, and break out of that many loops. The '''break''' command is effectively equivalent to "1 break_x". <br />
<br />
Thus, we add five more keywords to the list of Henceforth primitives: '''do''', '''if''', '''else''', '''break''', and '''continue'''. Brackets are used to denote code blocks where relevant. We will cover the syntax in simple examples. The first is a user-defined subroutine, "divide_safe", which avoids dividing by zero, and the second is an example of the Fibonacci sequence using "do" to achieve the effect of a "while" loop.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Save Divide-by-Zero Handling'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\divide_safe{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Do a zero-check on the divisor<br>&nbsp;&nbsp;&nbsp;&nbsp;dup&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;#Now, branch on that<br>&nbsp;&nbsp;&nbsp;&nbsp;if{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pop&nbsp;&nbsp;pop&nbsp;&nbsp;0<br>&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;div<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Note that returning zero is just one way of handling the erroneous case. Also, take note of the two "pop" commands; these are required!<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Fibonacci Numbers'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\fibonacci{<br>&nbsp;&nbsp;&nbsp;&nbsp;@count&nbsp;&nbsp;&nbsp;&nbsp; #Must be >=1<br>&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;@fprev&nbsp;&nbsp;#fib(n-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;@fnow&nbsp;&nbsp; #fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Test-and-decrement<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;-1&nbsp;&nbsp;add&nbsp;&nbsp;@count<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Iterate & Accumulate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprev@&nbsp;&nbsp;fnow@&nbsp;&nbsp; add<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fnow@&nbsp;&nbsp; @fprev<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fnow<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Re-do the loop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fnow@<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This subroutine fails given zero as input. Modify it by adding a simple if statement before the main loop. <br />
|}<br />
<br />
This second example is far more interesting; it answers the question "How do I Loop in Henceforth?"<br />
<br />
'''''A Note About Recursion''''': Although Henceforth fully supports recursion, the HVM is not designed for heavily-recursive functions. When possible, please use an iterative solution.<br />
<br />
<br />
<br />
==Henceforth Extensions for Scripting==<br />
<br />
The Henceforth specification recommends (read: ''does not require'') that the following pseudo-syntax be adopted to describe the commands which appear only in the '''Format B''' code for HSpeak script equivalents. A vanilla HVM should recognize these tokens and either ignore them, simulate them, or generate an informative error message when it encounters them.<br />
<br />
Backwards compatibility need not be preserved when converting between the '''Format B''' and '''Format T''' equivalents for script extensions.<br />
<br />
<br />
===The Three Formats of Henceforth===<br />
<br />
As mentioned, Henceforth source can appear in three different formats: T, B, and HF. We've been tossing these terms around without formally defining them. Here's a side-by-side comparison of the three.<br />
<br />
{|<br />
| &nbsp; || '''Type''' || '''Use'''<br />
|-<br />
| Format T || text || All human interaction (coding, discussing ideas, etc.) occurs in this format. Thins like script-local subroutines and function parameters cannot be expressed explicitly in this format.<br />
|-<br />
| Format B || bytecode (binary) || The HVM executes Format B code. The HSP2HF cross-compiler outputs this format. Contains Henceforth Extensions for Scripting.<br />
|-<br />
| Format HF || compressed || The XRPG specification requires all Format B code to be compressed into format HF lumps, which are then stored locally on the user's phone.<br />
|}<br />
<br />
<br />
===HFB===<br />
<br />
Format B code is detailed into exhaustion on a separate page:<br />
<br />
[[FMF:HFB]]<br />
<br />
<br />
<br />
===HSpeak API Calls & User Scripts===<br />
<br />
The HVM has knowledge of most "built-in" functions that Hamsterspeak provides. These are called like any other subroutine, with one exception: most format converters create specialized subroutine calls out of them. In other words, if I write a call to '''wait_npc''' in my ('''Format T''') code, the converter will create ('''Format B''') source that points ''explicitly'' to the definition of '''wait_npc''' in the HVM; it is '''''not''''' possible to overload '''wait_npc'''. This breaks our promise of fully-interchangeable source formats, but it is hardly noticeable to most users, and keeps our global function lookup table from becoming too cluttered.<br />
<br />
See [[FMF:Plotdict]] for a listing of all API calls and their syntax in Henceforth.<br />
<br />
User-defined scripts are treated exactly like API calls; they are assigned IDs and called explicitly. <br />
<br />
<br />
===Strings===<br />
<br />
Strings in Classic Forth are handled by simply pushing a series of integers (for each letter) to the stack. Eventually, Henceforth may go this route, but for now the API calls to strings are so simple that we chose a much simpler implementation:<br />
* Strings are a series of letters between double-quotes, like "Hello World", "Going?", or "Unicode: \u1000". As hinted by the last example, the HVM supports Unicode.<br />
* When a string is encountered (usually at the top of a script) it is pushed to the '''String Table''' for that script. Strings are pushed in order, cannot be removed or easily manipulated, and have IDs in increasing order.<br />
<br />
This is an ''ad hoc'' implementation of strings, until we get a clearer picture where the standard OHR's development is going with these ideas.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using a String'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;"Hello world!"<br>&nbsp;&nbsp;0 show_str</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Defining "Hello world!" will place this string at index zero; pushing 0 and then calling the API function '''show_str''' will reference that string explicitly.<br />
|}<br />
<br />
<br />
===Script-Local Subroutines===<br />
<br />
Here's where Henceforth starts to really get picky. When a Hamsterspeak script is converted to Henceforth, it is basically converted into a series of labeled blocks that call each other. Recalling that user-level "scripts" are effectively subroutines, the keen observer will note that a lot of resources are wasted supporting "subroutines" that are, actually, just glorified "goto" blocks. For example, a definition such as "\plus_two{ 2 add }" doesn't need its own stack space --it's just a snippet of code! <br />
<br />
With that in mind, subroutines are allowed to define "script-local" subroutines which operate along a single thread of control within a named block. These are defined with a given ID, and called with an array-like syntax. They cannot be un-defined or overloaded, although theoretically they could be replaced (by re-defining them).<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\[ID]{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\[5]{<br>&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;plus<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Our "plus two" example from before, assigned to index 5.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">[ID]()</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;16&nbsp;&nbsp;[5]()</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This pushes 16, and calls local block 5. The result is a stack containing the number 18, if we define [5] as listed above. ''The parentheses, although not technically necessary, do a great deal to visually distinguish script-local subroutines from function parameters.''<br />
|}<br />
<br />
<br />
===Local Parameters & Return Values===<br />
<br />
'''''MAKE A NOTE: Give an example of how my_script{ exp } calls "exp" ON the stack of my_script. '''''<br />
<br />
An astute reader will have noticed that scripts currently have no way of passing values to other scripts. Calling "5 6 my_func" won't work, because '''my_func''' will get its own independent stack. This problem is circumvented through the use of local parameters, which also provide a slight speedup for small, frequently called subroutines. The local parameters are accessed by ID, with -1 a special ID that refers to the script's return value. <br />
* Executing @[5] will "pop the (parent) stack to local parameter 5"<br />
* Executing [5]@ will "push local parameter 5 onto the (current) stack<br />
For the return value, it's backwards:<br />
* Executing @[-1] will "pop the (current) stack to the return value"<br />
* Executing [-1]@ will "push the return value onto the (parent) stack", '''''unless''''' the return value is set to ''invalid'' (INT_MIN, for now).<br />
The only point of interest is that last bit. The problem is, the HSpeak command "return" sets the return value, and "exitscript" will exit and return that value. Some scripts simply aren't intended to use their return values, which would leave an extra element on the stack. We could always just pop the return value, but we don't want to punish a highly modularized script with an un-necessary drag on performance. This is the alternative we came up with.<br />
<br />
===Comments===<br />
<br />
Henceforth supports comments, although it's important to realize that Formats B and HF will drop your comments; they can't be encoded. A comment begins with a hash mark (#) and ends with a line feed. <br />
<br />
<br />
===Global Variables===<br />
<br />
All that's left are global variables, which are manipulated with syntax like '''@[65.G]''' and '''[65.G]@'''. We realize this is a bit awkward, and we're open to a cleaner syntax (once we actually get a release going).<br />
<br />
= The Cross-Compiler =<br />
<br />
[[HSP2HF]]<br />
<br />
=Final Considerations=<br />
<br />
(later)<br />
<br />
<br />
<br />
<br />
<br />
<br />
{{Incomplete}}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=File:Hvm_thumb_bottom-up.png&diff=18617
File:Hvm thumb bottom-up.png
2009-01-15T07:17:31Z
<p>Sorlok reaves: /* Thumbnail picture for the bottom-up link on the HVM's page. */</p>
<hr />
<div>/* Thumbnail picture for the bottom-up link on the HVM's page. */</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=File:Hvm_thumb_top-down.png&diff=18616
File:Hvm thumb top-down.png
2009-01-15T07:12:05Z
<p>Sorlok reaves: /* A thumbnail for the HVM top-down page link. */</p>
<hr />
<div>/* A thumbnail for the HVM top-down page link. */</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Henceforth_VM&diff=18587
Henceforth VM
2009-01-03T04:17:27Z
<p>Sorlok reaves: /* WIP: Various minor edits, began re-shuffling */</p>
<hr />
<div>{{FMF}}<br />
<br />
The Henceforth Virtual Machine (HVM, Henceforth VM) is used to dynamically interpret Henceforth (HF, Hamsterforth) scripts. The OHRRPGCE FMF does not use Hamsterspeak's traditional HSX/HSZ files; rather, it opts for a stack-based alternative. Henceforth files are stored with the .HF extension.<br />
<br />
<br />
<div style="border: 2px red solid; width: 600px; padding: 10px"><span style="color:#AA0000; font-weight:bold; font-size: 20pt">Warning: </span> This document is complete, except for the "final considerations" section, which will be trivial to add later.<br>We are currently in the process of reviewing the entire design specification, writing a toy interpreter, and polishing up (and segmenting) this document. <br>We'll gladly take your suggestions on the "Discussion" page, but please do not modify, distribute, or trust this document. It will soon be as open to the community as everything else on this Wiki, but it'll take a few weeks to do the final review.<br><br>Thanks~<br>/Seth<br />
</div><br />
<br />
<br />
=Acknowledgements=<br />
<br />
A big thanks to both James and Ralph for their well-founded suggestions regarding the HVM, and to Mike for clarifying countless issues. If you start reading into the minute details of the HVM, you'll find that a whole lot of it is lean, clean, and sensible. This is almost entirely due to James & Ralph's suggestions and experience, which I truly appreciate. <br />
<br />
<br />
=Introduction to Henceforth=<br />
<br />
Henceforth is a simple, stack-based programming language which the OHRRPGCE FMF uses as a crossover language from traditional Hamsterspeak. Like all of the lumps exclusive to this project, .[[HF]] lumps containing Henceforth code are cross-compiled automatically for you when you convert your game to XRPG Format. However, all developers should consider learning basic Henceforth syntax to aid them in writing run-time debug scripts and compile-time optimized versions of their most time-critical Hamsterspeak scripts. <br />
<br />
Henceforth is a stack-based language, designed to obviate register allocation problems and minimize script size. You should read James's [[How_does_plotscripting_work%3F#The_Stack | excellent analogy of a stack]], if you aren't familiar with the concept. Hamsterspeak uses the stack for ordering scripts; Henceforth, however, uses the stack for storing data.<br />
<br />
<br />
==A Henceforth Primer==<br />
<br />
Although Henceforth is not a compiled language, you can pretend that "source" Henceforth is compiled into "binary" HFZ lumps, which the game engine runs, and only [[User:Sorlok_reaves | one person]] will cringe and correct you. So let's have a look at some Henceforth source code, shall we? Any text after a [http://dictionary.reference.com/browse/hash%20mark hash mark] until the next newline is considered a comment, and [http://dictionary.reference.com/browse/whitespace whitespace] is used to separate commands. Consider the following script.<br />
#One way to separate commands is by hitting "Enter" after each command.<br />
4 #Push 4<br />
5 #Push 5<br />
add #Pop the top two elements and add them. Push the result.<br />
@result #Pop the top element and store it in the variable "result".<br />
<br />
#Another way to separate commands is using spaces.<br />
4 5 add @result<br />
Most programmers will use a mix of spaces and newlines to keep their scripts clear and sensible. For example, "4 5 add" should probably always be on the same line. Regardless of what format you use, manipulation of the stack is the same. The following diagram shows how the stack (and the variables list) changes as the HVM reads each command, from left to right.<br />
<br />
[[Image:Fmf_stack_diagram.png]]<br />
<br />
<br />
==The "Pure" Virtual Machine==<br />
<br />
From a theoretical point of view, Henceforth does not require a location to store variables. Rather, we could simply add '''block labels''' and a delayed execution ('''prepending''') command:<br />
* Surrounding some source with '''\name{}''' will label that block with "name"; if '''name''' later appears as a command, this block will be inserted in-place. Later writing '''/name''' will un-define "name". If multiple blocks are labeled similarly, the most-recent label is considered valid. <br />
* Pre-pending "&" to a command will push the result of that command ''into the source code itself'', at the time that command is first encountered. <br />
<br />
'''Block labels''' allow functions to be written very naturally:<br />
#The "modulo" function expects two arguments to be<br />
# on the stack; it transforms (X Y) to (X%Y)<br />
\modulo {<br />
swap dup rot dup rot swap #Now we have (X Y X Y)<br />
div mult sub #The heart of our algorithm<br />
}<br />
There's no need to "return" anything; the labeled block operates in a way that leaves one item on the stack. Here's some code to use this block as a function:<br />
#We want to compute 7%3<br />
7 3 modulo #After this, "1" is left on the stack<br />
<br />
This is almost the exact syntax for writing a function in Henceforth. Now, let's cover '''prepending''':<br />
#Avoiding variables with named blocks.<br />
4 5 add <br />
\result{ #Label the start of the "result" block<br />
&pop #This is tricky: the first time we encounter this<br />
# (i.e., when "\result" is defined), it executes "pop",<br />
# resulting in "9", which is pushed into the source.<br />
} #Closes the "\result" label.<br />
<br />
result #Calling "result" will now push "9".<br />
<br />
Unlike named blocks, prepending is not actually implemented in Henceforth; you can ignore that last bit of code if you don't understand it. Which brings us to our main point:<br />
<br />
<big><big style="color:green;"><br />
'''Henceforth was designed around a "pure" VM'''</big><big>, but it was then modified to </big><big style="color:green;">'''run efficiently'''</big><big> on a phone, and to </big><big style="color:green;">'''allow an easy mapping'''</big><big> from existing Hamsterspeak scripts. <br />
</big></big><br />
<br />
It is crucial that you keep the ideal virtual machine in mind, or else you will start asking about "Henceforth's registers" (which don't exist) or you'll start using variables way too much. You also need to be cognizant of the dirty details required to kludge Hamsterspeak onto a phone; otherwise, things like script-level subroutines and our extravagant caching mechanism will seem redundant and senseless.<br />
<br />
(Note to readers: At this point, if you're only interested in the Henceforth language, you can skip down to the section on [[Henceforth_VM#Henceforth Syntax Dictionary | Henceforth Syntax]].)<br />
<br />
<br />
==Where the HVM Fits Into the Game Loop==<br />
<br />
Let's pretend that on a certain map in your game, a certain NPC is set up to "run script 4" when the user walks up to it and presses "Accept". Upon pressing "Accept", the game engine checks if this NPC has a script ID assigned to its "accept" behavior. The classic OHR performs this same task. If one exists, it is initialized and cached. At this point, the game engine continues with its tasks for that cycle (e.g., message boxes, vehicles). This (including user input) constitutes the "Update" phase of the game loop. Following this is the "Paint" phase, which shows one frame's worth of updates to the user. After painting, the tick count is incremented, and the loop repeats. The first thing that happens then is the "scripts" phase, during which scripts are actually executed (see below). The loop repeats indefinitely.<br />
<br />
[[Image:Hf_flowchart.png]]<br />
<br />
<br />
<br />
=Virtual Hardware Specification=<br />
<br />
The [http://en.wikipedia.org/wiki/Virtual_machine Wikipedia page] for Virtual Machines focuses on their use in emulation. In fact, there is no hardware version of the HVM, just like there is no hardware equivalent of the Java Virtual Machine. Rather, Henceforth defines an unambiguous virtual hardware specification which can be ported to any platform with guaranteed interoperability. Currently, the only platform with a working HVM is the OHRRPGCEFMF, due to the bias of the HVM's design towards mobile devices.<br />
<br />
Anything in the world can be described by its nouns and verbs --the things it '''has''' and what they '''do'''. So let's inspect the pieces of the HVM and how they interact.<br />
<br />
<br />
==VM Data Structures==<br />
<br />
[[Image:Fmf_hvm_data_structures.png]]<br />
<br />
Green and red lines in this diagram indicate references from one item to another, although they are not drawn for every single reference (because that would be craaaaaaazy). <br />
<br />
The Script UDT block is particularly garish, because it is accommodating two different script types: user-defined scripts and named subroutines. Each component is colored differently depending on its use.<br />
<br />
<br />
=== What these do, top-down ===<br />
<br />
One way of learning how these data structures work is by discussing what each piece does on its own, and then assuming their combination. This is like studying one of those [http://www.applesource.com/peeler.html apple machines]; we say that this piece pierces the apple and holds it, that piece slices the skin off, and that blade cores the apple.<br />
<br />
[[FMF:The_HVM,_From_the_Top_Down]]<br />
<br />
===What These Do: Bottom-Up Description===<br />
<br />
If you've ever seen an [http://www.applesource.com/peeler.html apple machine] like the one just mentioned, you're probably aware that describing each element does absolutely nothing to help you understand how the thing works --you have to actually use it on an apple (preferably very slowly) to see how the magic is done. This is a bottom-up description: describe how certain parts '''interact''' at critical moments in the operation of the whole.<br />
<br />
[[FMF:The_HVM,_From_the_Bottom_Up]]<br />
<br />
==Some Technical Considerations==<br />
<br />
When a script is triggered by any means, it suspends the currently-executing script; see "The Stack" in "[[How does plotscripting work?]]". This makes sense; if your script calls another script, one expects the new script to take over. It also means that if you step on several map tiles which each have (long) scripts, the last maptile's script will finish first. (Threading will probably change this.)<br />
<br />
But wait, there's more! Several small but important details of script initialization include:<br />
# If you trigger the same script twice (e.g., by stepping on two copies of an NPC one after the other) the script loader will detect that you are trying to load a script with the '''same id''' as the currently executing script. If the "Permit double-triggering of scripts" general bitset is on (or if the script is explicitly called by ''another script'') then this is allowed, and a second instance of the script is initialized. <br />
# When a script instance is '''first''' loaded, its "delay" counter is set to 2 ticks. This is ''not'' the same thing as instructing the script to "wait", because it remains the active script while delaying. If another script pre-empts this one, the delay counter does not decrement until the new script finishes. (When the new script finishes, the old one immediately resumes what it was doing, be it running or decrementing the "delay" counter.) <br />
# To prepare for parallel scripts, the active script is pushed to the top of the "waiting" stack, and is manipulated by a reference ID. This way, all scripts are kept in one place, and GAME_FMF can just scan through this list once each cycle, updating all scripts until they enter a waiting state.<br />
<br />
=HF Source Code & Formats=<br />
<br />
Strictly speaking, Henceforth has three different formats for its source code, and there is no such thing as "compiled Henceforth". These three formats are '''Format T''' (text-based), '''Format B''' (bytecode), and '''Format HF''' (compressed). Practically, most user code is generated in '''Format T''', which is then converted to '''Format B''' and compressed (to '''Format HF''') for storage. The HVM reads all three formats, but in doing so it converts T & HF to B. <br />
<br />
That said, we shall begin our study of Henceforth syntax by exhaustively reviewing all Henceforth commands in "source code" format ('''Format T'''), and then describe the mappings to other formats. <br />
<br />
Henceforth source is a rather slim language. Numbers and strings are supported, but concatenations like "Hello"+" world" and decimals like 2.4 are not. A syntax exists for "do" and "if" statements, and for declaring functions and variables. Several smaller hacks exist for efficiency's sake, as described earlier. And that's it. Anything else is either a "primitive" like '''add''', or a "block label", subject to the polymorphic lookup that characterizes Forth-like languages. Henceforth is not based explicitly on Classic Forth, but it hopes to learn from the mistakes of this peculiar family of languages. One of the main disadvantages to ANSI Forth is its pandering to ANSI C fixations, overcomplicating a language which, at its heart, always championed simplicity and elegance.<br />
<br />
<br />
==Henceforth Syntax Dictionary==<br />
<br />
This section contains all of the commands that you could enter into a run-time interpreter (if such a one existed). There is no reference to HSpeak scripts, GAME API calls, or anything else; this section simply explains Henceforth as a language.<br />
<br />
Think of it this way: A Henceforth "script" (called a subroutine) is a slimmed-down version of a Henceforth HSpeak "script" (called a script). A script maintains a separate stack, for one thing, and script-local subroutines, for another. So, by way of example, calling "wait" within a subroutine would cause the HVM to crash.<br />
<br />
If you're not fully sick of the differences yet, consider this: A script cannot be specified in textual format (that's only a recommended part of the specification, not required) while a subroutine is easy to explain in textual format. We hope to clarify this section in the future. We'll get back to you.<br />
<br />
<br />
===Numbers===<br />
<br />
Numbers are represented by signed integers such as 2, 18, -35, or 43. When the HVM encounters a number, it pushes it to the stack. That's all for numbers, so let's cover stack effect diagrams briefly; they're necessary to understand more complicated features. Consider:<br />
{| border="1" cellpadding="3" rules="all"<br />
|-<br />
| <tt style="font-size:10pt">1 2 3</tt>|| <tt style="font-size:10pt">7</tt> || <tt style="font-size:10pt">1 2 3 7</tt><br />
|}<br />
<br />
'''''Note:''''' Stack-effect diagrams are oriented with the top of the stack on the right.<br />
This means that the stack starts with the elements 1, 2, and 3 already in it, is then given the command "7", and the resulting stack is now 1, 2, 3, then 7. These is an example of the ''prototype'' style of learning; to teach you how to use a command, I give you a real-world example of how it works. <br />
<br />
If I want to write a stack effect diagram without the fancy table, I might use parentheses and the "--" symbol to mean "the operator we're talking about". For example: "if the interpreter encounters -5, the resulting stack-effect diagram would be <tt style="font-size:10pt">(1 2 3 -- 1 2 3 -5)</tt>". <br />
<br />
And now we're ''way'' beyond numbers.<br />
<br />
<br />
===Primitives===<br />
<br />
When the HVM interprets a literal string like '''exp''', '''mycode''', or '''abs''', it searches for the relevant subroutine. The primitives are exceptions to this; they are always executed immediately. A primitive consists of lowercase letters and underscores, and must contain at least one letter. We call this combination its '''symbol name'''.<br />
<br />
Here is a list of primitives, and their stack-effect diagrams:<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Primitive''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="5"| Stack Manipulation || <tt style="font-size:10pt">dup</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 3 )</tt> || "Duplicate" the top of the stack.<br />
|-<br />
| <tt style="font-size:10pt">swap</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 3 2 )</tt> || "Swap" the top two elements of the stack.<br />
|-<br />
| <tt style="font-size:10pt">drop</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 )</tt> || "Drop" the top element off the stack.<br />
|-<br />
| <tt style="font-size:10pt">over</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 2 )</tt> || Pull the penultimate element "over" the top element.<br />
|-<br />
| <tt style="font-size:10pt">rot</tt> || <tt style="font-size:10pt">( 1 2 3 4 -- 1 4 3 2 )</tt> || "Rotate" the top three elements of the stack.<br />
|-<br />
| rowspan="5"| Arithmetic || <tt style="font-size:10pt">add</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 5 )</tt> || Addition<br />
|-<br />
| <tt style="font-size:10pt">sub</tt> || <tt style="font-size:10pt">( 7 3 2 -- 7 1 )</tt> || Subtraction<br />
|-<br />
| <tt style="font-size:10pt">mult</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 6 )</tt> || Multiplication<br />
|-<br />
| <tt style="font-size:10pt">div</tt> || <tt style="font-size:10pt">( 1 7 3 -- 1 2 )</tt> || Division<br />
|-<br />
| <tt style="font-size:10pt">random</tt> || <tt style="font-size:10pt">( 1 4 8 -- 1 6 )</tt> || Random Number<br />
|-<br />
| rowspan="4"| Bitwise Operators || <tt style="font-size:10pt">b_xor</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 17 )</tt> || Exclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_or</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 53 )</tt> || Inclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_and</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 36 )</tt> || Bitwise "And"<br />
|-<br />
| <tt style="font-size:10pt">b_not</tt> || <tt style="font-size:10pt">( 1 52 -- 1 4294967243 )</tt> || Bitwise "Not"<br />
|-<br />
| rowspan="6"| Logical Operators || <tt style="font-size:10pt">eq</tt> || <tt style="font-size:10pt">( 1 2 2 -- 1 1 )</tt> || Logical "Equals"<br />
|-<br />
| <tt style="font-size:10pt">lt</tt> || <tt style="font-size:10pt">( 1 5 2 -- 1 0 )</tt> || Logical "Less Than"<br />
|-<br />
| <tt style="font-size:10pt">not</tt> || <tt style="font-size:10pt">( 1 5 1 -- 1 5 0 )</tt> || Logical "Not"<br />
|-<br />
| <tt style="font-size:10pt">and</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 1 )</tt> || Logical "And"<br />
|-<br />
| <tt style="font-size:10pt">or</tt> || <tt style="font-size:10pt">( 1 5 0 1 -- 1 5 1 )</tt> || Inclusive Logical "Or"<br />
|-<br />
| <tt style="font-size:10pt">xor</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 0 )</tt> || Exclusive Logical "Or"<br />
|}<br />
<br />
There is no boolean type; the number zero is considered "false", and any other number is considered "true". For clarity, the number "1" is the preferred way to represent "true".<br />
<br />
The primitives "and" and "or" do ''not'' short-circuit. This is because a short-circuiting operator behaves ambiguously for stack-based languages, and achieves no gain in performance or security (since it operates on values which have already been computed.) The [[HSP2HF || Cross-Compiler]] has an example of how to perform short-circuiting calculations in a manual way; see the section on Mathematical Functions.<br />
<br />
A number of additional keywords such as '''break''' and '''continue''' will be discussed in the section on If statements and Loops.<br />
<br />
===Blocks (Subroutines)===<br />
<br />
Labeled blocks, also called subroutines, are the core of Henceforth's modularization. They are strikingly similar to primitives, except that they can be defined, undefined, and overloaded. Labels contain letters and underscores, just like primitives.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This creates a function called "mod", which performs a series of stack manipulations and basic arithmetic.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;7 3 mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Traditionally, we push the "parameters" to the subroutine in increasing order, and expect it to remove these arguments (and replace them with a return value, if any) during the course of its operation.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Overriding a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;drop drop 0<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;After executing this snippet, "mod" will have two definitions. Calling "mod" will result in the latter being evoked, which simply clears the parameters and returns zero.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Un-defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">/NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;/mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;If we execute the code from "Overriding a Subroutine", and then execute this code, "mod" will be left with a single definition: the first one.<br />
|}<br />
<br />
<br />
Any labeled block remains in effect even if the script which defined it finishes execution; make sure to un-define any subroutines you do not wish to linger. The HVM capitalizes on this behavior to limit the number of built-in primitives. The modulus operator described earlier is an example, as is "less than or equal to", which can be defined compositely. Here is a list of all the subroutines the HVM loads by default, with their stack-effect diagrams.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Subroutine''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="2"| Arithmetic || <tt style="font-size:10pt">exp</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 8 )</tt> || Compute x raised to the y ("exponent")<br />
|-<br />
| <tt style="font-size:10pt">mod</tt> || <tt style="font-size:10pt">( 1 10 4 -- 1 2 )</tt> || Calculate the "modulus"<br />
|-<br />
| rowspan="4"| Logical Operators || <tt style="font-size:10pt">neq</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 1 )</tt> || Logical "Not Equal To"<br />
|-<br />
| <tt style="font-size:10pt">lte</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 1 )</tt> || Logical "Less Than or Equal To"<br />
|-<br />
| <tt style="font-size:10pt">gt</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 0 )</tt> || Logical "Greater Than"<br />
|-<br />
| <tt style="font-size:10pt">gte</tt> || <tt style="font-size:10pt">( 1 63 45 -- 1 1 )</tt> || Logical "Greater Than or Equal To"<br />
|-<br />
| rowspan="2"| Convenience Methods || <tt style="font-size:10pt">true</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 45 52 1 )</tt> || Locus-of-control for "true"<br />
|-<br />
| <tt style="font-size:10pt">false</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 45 45 0 )</tt> || Locus-of-control for "false"<br />
|}<br />
<br />
<br />
===Variables===<br />
<br />
Shuffling the stack beyond the fourth element can get messy. As such, subroutines can allocate ''local variables'' which stay in scope as long as that script is running. Note that variables follow a similar naming convention to subroutines. They are accessed using the @VAR and VAR@ commands. You can remember them easily by putting an imaginary "stack" next to them: <br />
* '''(stack)@VAR''' takes a value from the stack and throws it '''at''' VAR. This is the same as setting VAR = pop().<br />
* '''VAR@(stack)''' takes the value in VAR and throws it '''at''' the stack. This is the same as pushing VAR.<br />
<br />
Here is some sample source for the '''lte''' function, written using variables. Note that, in reality, '''lte''' only uses the stack. This is much more efficient for the HVM, which only initializes the local variable store if it detects your script trying to access it.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using Variables'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\lte{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Pop the stack twice to get our local variables.<br>&nbsp;&nbsp;&nbsp;&nbsp;#We assume that we are testing @lhs <= @rhs<br>&nbsp;&nbsp;&nbsp;&nbsp;@rhs&nbsp;&nbsp;@lhs<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the first test (<) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res1<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;lt&nbsp;&nbsp;@res1<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the second test (==) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res2<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;eq&nbsp;&nbsp;@res2<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our two tests and do an or test. Leave it on<br>&nbsp;&nbsp;&nbsp;&nbsp;# the stack to be returned<br>&nbsp;&nbsp;&nbsp;&nbsp;res1@&nbsp;&nbsp;res2@&nbsp;&nbsp;or<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;As an exercise, you should try to convert this to a stack-based alternative. It should end up more concise, and also faster.<br />
|}<br />
<br />
<br />
===Do/If & Loops===<br />
<br />
Most Forth-like languages have an "if" statement, and Henceforth is no exception. However, rather than encapsulating loops into labeled blocks (like Classic Forth), it adds another keyword: "do". This serves two purposes: first, it allows for better inlining than subroutines, and second, it provides a locus of control for jump commands. The second rationale is sometimes written as "otherwise, we'd have to use '''goto'''".<br />
<br />
Inside of a do loop, the commands '''break''' and '''continue''' also take effect; you should read [[HSZ#Id_11:_break | the HSZ documentation]] for an example of how these work conceptually. The mechanism for '''break''' and '''continue''' is different in the HVM, but the effect is the same.<br />
<br />
The standard Hamsterspeak engine also allows breaking out of multiple loops at once. In order to keep the syntax clean in Henceforth, we introduce two bytecodes for that purpose: '''break_x''' and '''continue_x'''. These take the element from the top of the stack, and break out of that many loops. The '''break''' command is effectively equivalent to "1 break_x". <br />
<br />
Thus, we add five more keywords to the list of Henceforth primitives: '''do''', '''if''', '''else''', '''break''', and '''continue'''. Brackets are used to denote code blocks where relevant. We will cover the syntax in simple examples. The first is a user-defined subroutine, "divide_safe", which avoids dividing by zero, and the second is an example of the Fibonacci sequence using "do" to achieve the effect of a "while" loop.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Save Divide-by-Zero Handling'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\divide_safe{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Do a zero-check on the divisor<br>&nbsp;&nbsp;&nbsp;&nbsp;dup&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;#Now, branch on that<br>&nbsp;&nbsp;&nbsp;&nbsp;if{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pop&nbsp;&nbsp;pop&nbsp;&nbsp;0<br>&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;div<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Note that returning zero is just one way of handling the erroneous case. Also, take note of the two "pop" commands; these are required!<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Fibonacci Numbers'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\fibonacci{<br>&nbsp;&nbsp;&nbsp;&nbsp;@count&nbsp;&nbsp;&nbsp;&nbsp; #Must be >=1<br>&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;@fprev&nbsp;&nbsp;#fib(n-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;@fnow&nbsp;&nbsp; #fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Test-and-decrement<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;-1&nbsp;&nbsp;add&nbsp;&nbsp;@count<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Iterate & Accumulate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprev@&nbsp;&nbsp;fnow@&nbsp;&nbsp; add<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fnow@&nbsp;&nbsp; @fprev<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fnow<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Re-do the loop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fnow@<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This subroutine fails given zero as input. Modify it by adding a simple if statement before the main loop. <br />
|}<br />
<br />
This second example is far more interesting; it answers the question "How do I Loop in Henceforth?"<br />
<br />
'''''A Note About Recursion''''': Although Henceforth fully supports recursion, the HVM is not designed for heavily-recursive functions. When possible, please use an iterative solution.<br />
<br />
<br />
<br />
==Henceforth Extensions for Scripting==<br />
<br />
The Henceforth specification recommends (read: ''does not require'') that the following pseudo-syntax be adopted to describe the commands which appear only in the '''Format B''' code for HSpeak script equivalents. A vanilla HVM should recognize these tokens and either ignore them, simulate them, or generate an informative error message when it encounters them.<br />
<br />
Backwards compatibility need not be preserved when converting between the '''Format B''' and '''Format T''' equivalents for script extensions.<br />
<br />
<br />
===The Three Formats of Henceforth===<br />
<br />
As mentioned, Henceforth source can appear in three different formats: T, B, and HF. We've been tossing these terms around without formally defining them. Here's a side-by-side comparison of the three.<br />
<br />
{|<br />
| &nbsp; || '''Type''' || '''Use'''<br />
|-<br />
| Format T || text || All human interaction (coding, discussing ideas, etc.) occurs in this format. Thins like script-local subroutines and function parameters cannot be expressed explicitly in this format.<br />
|-<br />
| Format B || bytecode (binary) || The HVM executes Format B code. The HSP2HF cross-compiler outputs this format. Contains Henceforth Extensions for Scripting.<br />
|-<br />
| Format HF || compressed || The XRPG specification requires all Format B code to be compressed into format HF lumps, which are then stored locally on the user's phone.<br />
|}<br />
<br />
<br />
===HFB===<br />
<br />
Format B code is detailed into exhaustion on a separate page:<br />
<br />
[[FMF:HFB]]<br />
<br />
<br />
<br />
===HSpeak API Calls & User Scripts===<br />
<br />
The HVM has knowledge of most "built-in" functions that Hamsterspeak provides. These are called like any other subroutine, with one exception: most format converters create specialized subroutine calls out of them. In other words, if I write a call to '''wait_npc''' in my ('''Format T''') code, the converter will create ('''Format B''') source that points ''explicitly'' to the definition of '''wait_npc''' in the HVM; it is '''''not''''' possible to overload '''wait_npc'''. This breaks our promise of fully-interchangeable source formats, but it is hardly noticeable to most users, and keeps our global function lookup table from becoming too cluttered.<br />
<br />
See [[FMF:Plotdict]] for a listing of all API calls and their syntax in Henceforth.<br />
<br />
User-defined scripts are treated exactly like API calls; they are assigned IDs and called explicitly. <br />
<br />
<br />
===Strings===<br />
<br />
Strings in Classic Forth are handled by simply pushing a series of integers (for each letter) to the stack. Eventually, Henceforth may go this route, but for now the API calls to strings are so simple that we chose a much simpler implementation:<br />
* Strings are a series of letters between double-quotes, like "Hello World", "Going?", or "Unicode: \u1000". As hinted by the last example, the HVM supports Unicode.<br />
* When a string is encountered (usually at the top of a script) it is pushed to the '''String Table''' for that script. Strings are pushed in order, cannot be removed or easily manipulated, and have IDs in increasing order.<br />
<br />
This is an ''ad hoc'' implementation of strings, until we get a clearer picture where the standard OHR's development is going with these ideas.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using a String'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;"Hello world!"<br>&nbsp;&nbsp;0 show_str</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Defining "Hello world!" will place this string at index zero; pushing 0 and then calling the API function '''show_str''' will reference that string explicitly.<br />
|}<br />
<br />
<br />
===Script-Local Subroutines===<br />
<br />
Here's where Henceforth starts to really get picky. When a Hamsterspeak script is converted to Henceforth, it is basically converted into a series of labeled blocks that call each other. Recalling that user-level "scripts" are effectively subroutines, the keen observer will note that a lot of resources are wasted supporting "subroutines" that are, actually, just glorified "goto" blocks. For example, a definition such as "\plus_two{ 2 add }" doesn't need its own stack space --it's just a snippet of code! <br />
<br />
With that in mind, subroutines are allowed to define "script-local" subroutines which operate along a single thread of control within a named block. These are defined with a given ID, and called with an array-like syntax. They cannot be un-defined or overloaded, although theoretically they could be replaced (by re-defining them).<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\[ID]{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\[5]{<br>&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;plus<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Our "plus two" example from before, assigned to index 5.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">[ID]()</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;16&nbsp;&nbsp;[5]()</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This pushes 16, and calls local block 5. The result is a stack containing the number 18, if we define [5] as listed above. ''The parentheses, although not technically necessary, do a great deal to visually distinguish script-local subroutines from function parameters.''<br />
|}<br />
<br />
<br />
===Local Parameters & Return Values===<br />
<br />
'''''MAKE A NOTE: Give an example of how my_script{ exp } calls "exp" ON the stack of my_script. '''''<br />
<br />
An astute reader will have noticed that scripts currently have no way of passing values to other scripts. Calling "5 6 my_func" won't work, because '''my_func''' will get its own independent stack. This problem is circumvented through the use of local parameters, which also provide a slight speedup for small, frequently called subroutines. The local parameters are accessed by ID, with -1 a special ID that refers to the script's return value. <br />
* Executing @[5] will "pop the (parent) stack to local parameter 5"<br />
* Executing [5]@ will "push local parameter 5 onto the (current) stack<br />
For the return value, it's backwards:<br />
* Executing @[-1] will "pop the (current) stack to the return value"<br />
* Executing [-1]@ will "push the return value onto the (parent) stack", '''''unless''''' the return value is set to ''invalid'' (INT_MIN, for now).<br />
The only point of interest is that last bit. The problem is, the HSpeak command "return" sets the return value, and "exitscript" will exit and return that value. Some scripts simply aren't intended to use their return values, which would leave an extra element on the stack. We could always just pop the return value, but we don't want to punish a highly modularized script with an un-necessary drag on performance. This is the alternative we came up with.<br />
<br />
===Comments===<br />
<br />
Henceforth supports comments, although it's important to realize that Formats B and HF will drop your comments; they can't be encoded. A comment begins with a hash mark (#) and ends with a line feed. <br />
<br />
<br />
===Global Variables===<br />
<br />
All that's left are global variables, which are manipulated with syntax like '''@[65.G]''' and '''[65.G]@'''. We realize this is a bit awkward, and we're open to a cleaner syntax (once we actually get a release going).<br />
<br />
= The Cross-Compiler =<br />
<br />
[[HSP2HF]]<br />
<br />
=Final Considerations=<br />
<br />
(later)<br />
<br />
<br />
<br />
<br />
<br />
<br />
{{Incomplete}}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=HSP2HF&diff=18574
HSP2HF
2008-12-30T02:26:29Z
<p>Sorlok reaves: /* A small section on the resolution engine */</p>
<hr />
<div>{{FMF}}<br />
<br />
Most of you will never have to touch Henceforth if you don't want to. (But who wouldn't want to?) The HSP2HF cross-compiler is used to automatically convert your HamsterSpeak into Henceforth bytecode. It is part of the RPG2XRPG conversion process.<br />
<br />
<br />
<br />
==How the Cross-Compiler Works: Motivations==<br />
<br />
Some parts of the OHRRPGCE FMF project are slightly incompatible with the standard OHRRPGCE, but for scripting this is simply unacceptable. Unlike things like, say, a slightly jittery enemy HP value -which is immediately apparent when you first load your game on a phone- a silent error in a script is worth hours of headache-inducing debugging, and probably not worth anything at all in the end. <br />
<br />
So, the goal of the cross-compiler is script-level compatibility. Efficiency and conciseness, while important, take a backseat to this driving need.<br />
<br />
<br />
==How the Cross-Compiler Works: Naive Compiler==<br />
<br />
The Henceforth cross-compiler benefits from HamsterSpeak's tree-like structure; a naive conversion can simply convert each node to a local Henceforth function. Consider the following script (from Wandering Hamster, loaded in the HSP2HF utility)<br />
<br />
[[Image:FMF_cross_compiler_screenshot.png]]<br />
<br />
Typical to most HSZ scripts, "setnpcspeed" contains a head "do" node. This node happens to contain only one element, which takes three arguments, each of which is a simple number or variable. Clicking "cross-compile" will invoke the naive converter, which produces the following code:<br />
<br />
\[14]{<br />
[1]@<br />
}<br />
\[12]{<br />
3<br />
}<br />
\[10]{<br />
[0]@<br />
}<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
<br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[4]()<br />
do_end<br />
<br />
Let's start from the local variables section:<br />
@[1]<br />
This is a shorthand syntax; it basically calls "local store", a function deep within the script interpreter which does something like this:<br />
void localStore(int arg) {<br />
local_variables[arg] = pop_parent();<br />
}<br />
<br />
Next, we have the main loop. The "do_start" and "do_end" primitives are there to help the "break" and "continue" primitives to function properly. The meat of the main loop is the call to:<br />
[4]()<br />
...which is simply a call to a script-local subroutine.<br />
<br />
The following code defines script-local subroutine 4:<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
...as three script-local calls([10], [12], and [14]) and one built-in function call ('''H'''amster'''S'''peak:78, alterNPC). The remaining local functions are equally easy to understand. For example, "[1]@" calls "local load" with "1" as an argument. Local load is defined internally as:<br />
void localLoad(arg1) {<br />
push(local_variables[arg1]);<br />
}<br />
<br />
==How the Cross-Compiler Works: Reasonable Inlining==<br />
<br />
Simple functions like "setnpcspeed" are very easy to inline, just by copying the leaf nodes' source into their parents. The previous script can be re-written as: <br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[0]@<br />
3<br />
[1]@<br />
[HS:78]()<br />
do_end<br />
...which is much more concise. Due to the nature of HF bytecode, inlining usually improves both performance and storage efficiency. (When I have time to profile, I hope to collect some facts to back this up.) However, inlining everything is often either impossible or unwise, which is why one needs a policy for inlining. At the moment, the OHRRPGCE FMF's cross-compiler uses the following algorithm to determine what to inline:<br />
1) Start by doing a naive conversion, and retain the tree structure. <br />
Mark all nodes as "not dead" and with a "count" of 1.<br />
2) Loop until all nodes are inlined or dead. At each iteration, do the following.<br />
3) Determine which nodes are leaf nodes. (Leaf nodes have ''no'' children, or only dead children).<br />
a) If this node cannot be inlined (e.g., it's self-referential, or checks b/c/d/e below fail), <br />
mark it as "dead".<br />
b) If this node's count is "1", inline it. (Copy its corresponding source into any node that <br />
references it, incrementing their "count" value by 1, and then delete it.)<br />
c) If this node's count is "2", and it is referenced 8 times or less, inline it.<br />
d) If this node's count is "3", and it is referenced 4 times or less, inline it.<br />
e) If this node's count is "4" or more, only inline it if it is referenced exactly once.<br />
<br />
We are still discussing what makes a node impossible to inline. Technically, the problem is difficult, but Hamsterspeak byte-code is fairly structured in nature, which means we can probably define a few simple criteria for exclusion. <br />
<br />
<br />
==Primitives & HSPEAK->HF Snippits==<br />
<br />
The cross-compiler inserts snippets of HF code when it encounters an HSPEAK node of a given type. For example, at node 10, given a "number" node with value '''75''', it inserts:<br />
<br />
\[10] {<br />
75<br />
}<br />
<br />
Henceforth, we shall refer to a node of ID n as: '''\[n] {}''' --this allows us to generalize HSPEAK nodes into simple templates. Just a reminder: '''\[n] {}''' represents a ''script-local subroutine''; it is ''not'' valid '''Format T''' syntax.<br />
<br />
The following templates are loaded when the HVM initializes, so the cross-compiler makes use of them to simplify syntax:<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" |'''''Templates'''''<br />
|-<br />
| valign="top" | <span style="color:#0000AA;font-weight:bold">#Template: -2 4 set_var will set local variable 1 to value 4</span><br>\set_var {<br>&nbsp;&nbsp;swap<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;@[]<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;@[.G]<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br><span style="color:#0000AA;font-weight:bold">#Template: 2 get_var will return the contents of global variable 2</span><br>\get_var {<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;[]@<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;[.G]@<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
Here are the snippets used by the cross-compiler; we repeat numbers for the sake of completeness:<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Numbers'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">number</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Do Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | do<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''If Statements'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | if<br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">then_y</span><br><span style="color:green;font-weight:bold">else_z</span><br />
| valign="top" | <br>\[n]{<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[z]()</span><br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Then/Else Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | then/else<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;...<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Break/Continue'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">command</span><br />
| valign="top" | <span style="color:green;font-weight:bold">amount</span><br>''if amount==1 then:''<br>&nbsp;&nbsp;''skip amount''<br>''else:''<br>&nbsp;&nbsp;''append "_x" to command''<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[amount]</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">command</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Returning'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">return</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;''invalid''<br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''While/For'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">while</span><br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;not if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">for</span><br />
| valign="top" | <span style="color:green;font-weight:bold">count_id_x</span><br><span style="color:green;font-weight:bold">counter_start_s</span><br><span style="color:green;font-weight:bold">count_end_e</span><br><span style="color:green;font-weight:bold">counter_step_w</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[s]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;set_var<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;0 gt<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;gt xor not<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;neq and<br>&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;add<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| colspan="4" | '''''Note 1:''''' ''The block <span style="color:green">inline_y</span> simply unrolls the <span style="color:green">do_y</span> block into the body of '''[n]()'''. This is done so that '''break''' and '''continue''' will function properly.'' <br>'''''Note 2:''''' ''The upshot of this is that <span style="color:green">do_y</span> will be instantly culled from the source, unless another node references it (which would be a bit of a hack, in my opinion. Regardless, this is fine, and will not affect program validity in any way.''<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Switch'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | switch<br />
| valign="top" | ???<br />
| valign="top" | ''This is not yet documented in [[HSZ]], so we will deal with it later.<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Variable Access'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">global</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[.G]@<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">local</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[]@<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Math Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">set_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">increment_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"></span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;add<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">decrement_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"></span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;sub<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">not</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;not<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">and</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;[r]()<br>&nbsp;&nbsp;} else { <br>&nbsp;&nbsp;&nbsp;&nbsp;False<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">or</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;not<br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;[r]()<br>&nbsp;&nbsp;} else { <br>&nbsp;&nbsp;&nbsp;&nbsp;True<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">operand</span><br>''If the operand is listed <br>above, use '''that''' code <br>block, not this one.''<br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">operand</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Built-In and User-Defined Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">built-in</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;hspeak_api_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">user-script</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;user_script_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|}<br />
<br />
<br />
<br />
==Resolution Engine==<br />
<br />
After cross-compiling, the HSP2HF utility is basically left with a sequence of bytecodes for each script. The final step is to lump these together into HF lumps. The size of a script, along with its potential to call other scripts, is used to properly group several scripts into one HF lump. To gather this information, a single pass of each script is made. Simultaneously, the resolution engine scans scripts to find simple optimizations which can be performed in place. A list of these optimizations follows.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" |'''''Found''''' <br />
|bgcolor="#C8FFC8" | '''''Replaced By''''' <br />
|bgcolor="#C8FFC8" | '''''Reasoning'''''<br />
|-<br />
| valign="top" | '''number'''&nbsp;&nbsp;get_var<br />
| valign="top" | ['''number'''.G]@ <span style="color:#0000AA;font-weight:bold">&nbsp;&nbsp;#If '''number''' is < 0</span><br> [-('''number'''+1)]@<span style="color:#0000AA;font-weight:bold">&nbsp;&nbsp;#Otherwise</span><br />
| valign="top" | The parameter ID is often known.<br />
|-<br />
| valign="top" | '''number'''&nbsp;&nbsp;'''value'''&nbsp;&nbsp;set_var<br />
| valign="top" | '''value'''&nbsp;&nbsp;@['''number'''.G] <span style="color:#0000AA;font-weight:bold">&nbsp;&nbsp;#If '''number''' is < 0</span><br> '''value'''&nbsp;&nbsp;@[-('''number'''+1)]<span style="color:#0000AA;font-weight:bold">&nbsp;&nbsp;#Otherwise</span><br />
| valign="top" | Ditto to the above.<br />
|}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=Henceforth_VM&diff=18573
Henceforth VM
2008-12-30T00:16:48Z
<p>Sorlok reaves: /* Edit: Why we don't short-circuit. */</p>
<hr />
<div>{{FMF}}<br />
<br />
The Henceforth Virtual Machine (HVM, Henceforth VM) is used to dynamically interpret Henceforth (HF, Hamsterforth) scripts. The OHRRPGCE FMF does not use Hamsterspeak's traditional HSX/HSZ files; rather, it opts for a stack-based alternative. Henceforth files are stored with the .HF extension.<br />
<br />
<br />
<div style="border: 2px red solid; width: 600px; padding: 10px"><span style="color:#AA0000; font-weight:bold; font-size: 20pt">Warning: </span> This document is complete, except for the "final considerations" section, which will be trivial to add later.<br>We are currently in the process of reviewing the entire design specification, writing a toy interpreter, and polishing up (and segmenting) this document. <br>We'll gladly take your suggestions on the "Discussion" page, but please do not modify, distribute, or trust this document. It will soon be as open to the community as everything else on this Wiki, but it'll take a few weeks to do the final review.<br><br>Thanks~<br>/Seth<br />
</div><br />
<br />
<br />
=Acknowledgements=<br />
<br />
A big thanks to both James and Ralph for their well-founded suggestions regarding the HVM, and to Mike for clarifying countless issues. If you start reading into the minute details of the HVM, you'll find that a whole lot of it is lean, clean, and sensible. This is almost entirely due to James & Ralph's suggestions and experience, which I truly appreciate. <br />
<br />
<br />
=Introduction to Henceforth=<br />
<br />
Henceforth is a simple, stack-based programming language which the OHRRPGCE FMF uses as a crossover language from traditional Hamsterspeak. Like all of the lumps exclusive to this project, .[[HF]] lumps containing Henceforth code are cross-compiled automatically for you when you convert your game to XRPG Format. However, all developers should consider learning basic Henceforth syntax to aid them in writing run-time debug scripts and compile-time optimized versions of their most time-critical Hamsterspeak scripts. <br />
<br />
Henceforth is a stack-based language, designed to obviate register allocation problems and minimize script size. You should read James's [[How_does_plotscripting_work%3F#The_Stack | excellent analogy of a stack]], if you aren't familiar with the concept. Hamsterspeak uses the stack for ordering scripts; Henceforth, however, uses the stack for storing data.<br />
<br />
<br />
==A Henceforth Primer==<br />
<br />
Although Henceforth is not a compiled language, you can pretend that "source" Henceforth is compiled into "binary" HFZ lumps, which the game engine runs, and only [[User:Sorlok_reaves | one person]] will cringe and correct you. So let's have a look at some Henceforth source code, shall we? Any text after a [http://dictionary.reference.com/browse/hash%20mark hash mark] until the next newline is considered a comment, and [http://dictionary.reference.com/browse/whitespace whitespace] is used to separate commands. Consider the following script.<br />
#One way to separate commands is by hitting "Enter" after each command.<br />
4 #Push 4<br />
5 #Push 5<br />
add #Pop the top two elements and add them. Push the result.<br />
@result #Pop the top element and store it in the variable "result".<br />
<br />
#Another way to separate commands is using spaces.<br />
4 5 add @result<br />
Most programmers will use a mix of spaces and newlines to keep their scripts clear and sensible. For example, "4 5 add" should probably always be on the same line. Regardless of what format you use, manipulation of the stack is the same. The following diagram shows how the stack (and the variables list) changes as the HVM reads each command, from left to right.<br />
<br />
[[Image:Fmf_stack_diagram.png]]<br />
<br />
<br />
==The "Pure" Virtual Machine==<br />
<br />
From a theoretical point of view, Henceforth does not require a location to store variables. Rather, we could simply add '''block labels''' and a delayed execution ('''prepending''') command:<br />
* Surrounding some source with '''\name{}''' will label that block with "name"; if '''name''' later appears as a command, this block will be inserted in-place. Later writing '''/name''' will un-define "name". If multiple blocks are labeled similarly, the most-recent label is considered valid. <br />
* Pre-pending "&" to a command will push the result of that command ''into the source code itself'', at the time that command is first encountered. <br />
<br />
'''Block labels''' allow functions to be written very naturally:<br />
#The "modulo" function expects two arguments to be<br />
# on the stack; it transforms (X Y) to (X%Y)<br />
\modulo {<br />
swap dup rot dup rot swap #Now we have (X Y X Y)<br />
div mult sub #The heart of our algorithm<br />
}<br />
There's no need to "return" anything; the labeled block operates in a way that leaves one item on the stack. Here's some code to use this block as a function:<br />
#We want to compute 7%3<br />
7 3 modulo #After this, "1" is left on the stack<br />
<br />
This is almost the exact syntax for writing a function in Henceforth. Now, let's cover '''prepending''':<br />
#Avoiding variables with named blocks.<br />
4 5 add <br />
\result{ #Label the start of the "result" block<br />
&pop #This is tricky: the first time we encounter this<br />
# (i.e., when "\result" is defined), it executes "pop",<br />
# resulting in "9", which is pushed into the source.<br />
} #Closes the "\result" label.<br />
<br />
result #Calling "result" will now push "9".<br />
<br />
Unlike named blocks, prepending is not actually implemented in Henceforth; you can ignore that last bit of code if you don't understand it. Which brings us to our main point:<br />
<br />
<big><big style="color:green;"><br />
'''Henceforth was designed around a "pure" VM'''</big><big>, but it was then modified to </big><big style="color:green;">'''run efficiently'''</big><big> on a phone, and to </big><big style="color:green;">'''allow an easy mapping'''</big><big> from existing Hamsterspeak scripts. <br />
</big></big><br />
<br />
It is crucial that you keep the ideal virtual machine in mind, or else you will start asking about "Henceforth's registers" (which don't exist) or you'll start using variables way too much. You also need to be cognizant of the dirty details required to kludge Hamsterspeak onto a phone; otherwise, things like script-level subroutines and our extravagant caching mechanism will seem redundant and senseless.<br />
<br />
(Note to readers: At this point, if you're only interested in the Henceforth language, you can skip down to the section on [[Henceforth_VM#Henceforth Syntax Dictionary | Henceforth Syntax]].)<br />
<br />
<br />
==Where the HVM Fits Into the Game Loop==<br />
<br />
Let's pretend that on a certain map in your game, a certain NPC is set up to "run script 4" when the user walks up to it and presses "Accept". Upon pressing "Accept", the game engine checks if this NPC has a script ID assigned to its "accept" behavior. The classic OHR performs this same task. If one exists, it is initialized and cached. At this point, the game engine continues with its tasks for that cycle (e.g., message boxes, vehicles). This (including user input) constitutes the "Update" phase of the game loop. Following this is the "Paint" phase, which shows one frame's worth of updates to the user. After painting, the tick count is incremented, and the loop repeats. The first thing that happens then is the "scripts" phase, during which scripts are actually executed (see below). The loop repeats indefinitely.<br />
<br />
[[Image:Hf_flowchart.png]]<br />
<br />
<br />
<br />
=Virtual Hardware Specification=<br />
<br />
The [http://en.wikipedia.org/wiki/Virtual_machine Wikipedia page] for Virtual Machines focuses on their use in emulation. In fact, there is no hardware version of the HVM, just like there is no hardware equivalent of the Java Virtual Machine. Rather, Henceforth defines an unambiguous virtual hardware specification which can be ported to any platform with guaranteed interoperability. Currently, the only platform with a working HVM is the OHRRPGCEFMF, due to the bias of the HVM's design towards mobile devices.<br />
<br />
Anything in the world can be described by its nouns and verbs --the things it '''has''' and what they '''do'''. So let's inspect the pieces of the HVM and how they interact.<br />
<br />
<br />
==VM Data Structures==<br />
<br />
[[Image:Fmf_hvm_data_structures.png]]<br />
<br />
Green and red lines in this diagram indicate references from one item to another, although they are not drawn for every single reference (because that would be craaaaaaazy). <br />
<br />
The Script UDT block is particularly garish, because it is accommodating two different script types: user-defined scripts and named subroutines. Each component is colored differently depending on its use.<br />
<br />
<br />
=== What these do, top-down ===<br />
<br />
One way of learning how these data structures work is by discussing what each piece does on its own, and then assuming their combination. This is like studying one of those [http://www.applesource.com/peeler.html apple machines]; we say that this piece pierces the apple and holds it, that piece slices the skin off, and that blade cores the apple.<br />
<br />
[[FMF:The_HVM,_From_the_Top_Down]]<br />
<br />
===What These Do: Bottom-Up Description===<br />
<br />
If you've ever seen an [http://www.applesource.com/peeler.html apple machine] like the one just mentioned, you're probably aware that describing each element does absolutely nothing to help you understand how the thing works --you have to actually use it on an apple (preferably very slowly) to see how the magic is done. This is a bottom-up description: describe how certain parts '''interact''' at critical moments in the operation of the whole.<br />
<br />
[[FMF:The_HVM,_From_the_Bottom_Up]]<br />
<br />
==Some Technical Considerations==<br />
<br />
When a script is triggered by any means, it suspends the currently-executing script; see "The Stack" in "[[How does plotscripting work?]]". This makes sense; if your script calls another script, one expects the new script to take over. It also means that if you step on several map tiles which each have (long) scripts, the last maptile's script will finish first. (Threading will probably change this.)<br />
<br />
But wait, there's more! Several small but important details of script initialization include:<br />
# If you trigger the same script twice (e.g., by stepping on two copies of an NPC one after the other) the script loader will detect that you are trying to load a script with the '''same id''' as the currently executing script. If the "Permit double-triggering of scripts" general bitset is on (or if the script is explicitly called by ''another script'') then this is allowed, and a second instance of the script is initialized. <br />
# When a script instance is '''first''' loaded, its "delay" counter is set to 2 ticks. This is ''not'' the same thing as instructing the script to "wait", because it remains the active script while delaying. If another script pre-empts this one, the delay counter does not decrement until the new script finishes. (When the new script finishes, the old one immediately resumes what it was doing, be it running or decrementing the "delay" counter.) <br />
# To prepare for parallel scripts, the active script is pushed to the top of the "waiting" stack, and is manipulated by a reference ID. This way, all scripts are kept in one place, and GAME_FMF can just scan through this list once each cycle, updating all scripts until they enter a waiting state.<br />
<br />
=HF Source Code & Formats=<br />
<br />
Strictly speaking, Henceforth has three different formats for its source code, and there is no such thing as "compiled Henceforth". These three formats are '''Format T''' (text-based), '''Format B''' (bytecode), and '''Format HF''' (compressed). Practically, most user code is generated in '''Format T''', which is then converted to '''Format B''' and compressed (to '''Format HF''') for storage. The HVM reads all three formats, but in doing so it converts T & HF to B. <br />
<br />
That said, we shall begin our study of Henceforth syntax by exhaustively reviewing all Henceforth commands in "source code" format ('''Format T'''), and then describe the mappings to other formats. <br />
<br />
Henceforth source is a rather slim language. Numbers and strings are supported, but concatenations like "Hello"+" world" and decimals like 2.4 are not. A syntax exists for "do" and "if" statements, and for declaring functions and variables. Several smaller hacks exist for efficiency's sake, as described earlier. And that's it. Anything else is either a "primitive" like '''add''', or a "block label", subject to the polymorphic lookup that characterizes Forth-like languages. Henceforth is not based explicitly on Classic Forth, but it hopes to learn from the mistakes of this peculiar family of languages. One of the main disadvantages to ANSI Forth is its pandering to C++ fixations, overcomplicating a language which, at its heart, always championed simplicity and elegance.<br />
<br />
<br />
==Henceforth Syntax Dictionary==<br />
<br />
===Numbers===<br />
<br />
Numbers are represented by signed integers such as 2, 18, -35, or 43. When the HVM encounters a number, it pushes it to the stack. That's all for numbers, so let's cover stack effect diagrams briefly; they're necessary to understand more complicated features. Consider:<br />
{| border="1" cellpadding="3" rules="all"<br />
|-<br />
| <tt style="font-size:10pt">1 2 3</tt>|| <tt style="font-size:10pt">7</tt> || <tt style="font-size:10pt">1 2 3 7</tt><br />
|}<br />
<br />
'''''Note:''''' Stack-effect diagrams are oriented with the top of the stack on the right.<br />
This means that the stack starts with the elements 1, 2, and 3 already in it, is then given the command "7", and the resulting stack is now 1, 2, 3, then 7. These is an example of the ''prototype'' style of learning; to teach you how to use a command, I give you a real-world example of how it works. <br />
<br />
If I want to write a stack effect diagram without the fancy table, I might use parentheses and the "--" symbol to mean "the operator we're talking about". For example: "if the interpreter encounters -5, the resulting stack-effect diagram would be <tt style="font-size:10pt">(1 2 3 -- 1 2 3 -5)</tt>". <br />
<br />
And now we're ''way'' beyond numbers.<br />
<br />
<br />
===Primitives===<br />
<br />
When the HVM interprets a literal string like '''exp''', '''mycode''', or '''abs''', it searches for the relevant subroutine. The primitives are exceptions to this; they are always executed immediately. A primitive consists of lowercase letters and underscores, and must contain at least one letter.<br />
<br />
Here is a list of primitives, and their stack-effect diagrams:<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Primitive''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="5"| Stack Manipulation || <tt style="font-size:10pt">dup</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 3 )</tt> || "Duplicate" the top of the stack.<br />
|-<br />
| <tt style="font-size:10pt">swap</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 3 2 )</tt> || "Swap" the top two elements of the stack.<br />
|-<br />
| <tt style="font-size:10pt">drop</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 )</tt> || "Drop" the top element off the stack.<br />
|-<br />
| <tt style="font-size:10pt">over</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 2 3 2 )</tt> || Pull the penultimate element "over" the top element.<br />
|-<br />
| <tt style="font-size:10pt">rot</tt> || <tt style="font-size:10pt">( 1 2 3 4 -- 1 4 3 2 )</tt> || "Rotate" the top three elements of the stack.<br />
|-<br />
| rowspan="5"| Arithmetic || <tt style="font-size:10pt">add</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 5 )</tt> || Addition<br />
|-<br />
| <tt style="font-size:10pt">sub</tt> || <tt style="font-size:10pt">( 7 3 2 -- 7 1 )</tt> || Subtraction<br />
|-<br />
| <tt style="font-size:10pt">mult</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 6 )</tt> || Multiplication<br />
|-<br />
| <tt style="font-size:10pt">div</tt> || <tt style="font-size:10pt">( 1 7 3 -- 1 2 )</tt> || Division<br />
|-<br />
| <tt style="font-size:10pt">random</tt> || <tt style="font-size:10pt">( 1 4 8 -- 1 6 )</tt> || Random Number<br />
|-<br />
| rowspan="4"| Bitwise Operators || <tt style="font-size:10pt">b_xor</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 17 )</tt> || Exclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_or</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 53 )</tt> || Inclusive Bitwise "Or"<br />
|-<br />
| <tt style="font-size:10pt">b_and</tt> || <tt style="font-size:10pt">( 1 52 37 -- 1 36 )</tt> || Bitwise "And"<br />
|-<br />
| <tt style="font-size:10pt">b_not</tt> || <tt style="font-size:10pt">( 1 52 -- 1 4294967243 )</tt> || Bitwise "Not"<br />
|-<br />
| rowspan="6"| Logical Operators || <tt style="font-size:10pt">eq</tt> || <tt style="font-size:10pt">( 1 2 2 -- 1 1 )</tt> || Logical "Equals"<br />
|-<br />
| <tt style="font-size:10pt">lt</tt> || <tt style="font-size:10pt">( 1 5 2 -- 1 0 )</tt> || Logical "Less Than"<br />
|-<br />
| <tt style="font-size:10pt">not</tt> || <tt style="font-size:10pt">( 1 5 1 -- 1 5 0 )</tt> || Logical "Not"<br />
|-<br />
| <tt style="font-size:10pt">and</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 1 )</tt> || Logical "And"<br />
|-<br />
| <tt style="font-size:10pt">or</tt> || <tt style="font-size:10pt">( 1 5 0 1 -- 1 5 1 )</tt> || Inclusive Logical "Or"<br />
|-<br />
| <tt style="font-size:10pt">xor</tt> || <tt style="font-size:10pt">( 1 5 1 1 -- 1 5 0 )</tt> || Exclusive Logical "Or"<br />
|}<br />
<br />
There is no boolean type; the number zero is considered "false", and any other number is considered "true". For clarity, the number "1" is the preferred way to represent "true".<br />
<br />
The primitives "and" and "or" do ''not'' short-circuit. This is because a short-circuiting operator behaves ambiguously for stack-based languages, and achieves no gain in performance or security (since it operates on values which have already been computed.) The [[HSP2HF || Cross-Compiler]] has an example of how to perform short-circuiting calculations in a manual way; see the section on Mathematical Functions.<br />
<br />
A number of additional keywords such as '''break''' and '''continue''' will be discussed in the section on If statements and Loops.<br />
<br />
===Blocks (Subroutines)===<br />
<br />
Labeled blocks, also called subroutines, are the core of Henceforth's modularization. They are strikingly similar to primitives, except that they can be defined, undefined, and overloaded. Labels contain letters and underscores, just like primitives.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This creates a function called "mod", which performs a series of stack manipulations and basic arithmetic.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;7 3 mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Traditionally, we push the "parameters" to the subroutine in increasing order, and expect it to remove these arguments (and replace them with a return value, if any) during the course of its operation.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Overriding a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\NAME{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;swap dup rot dup rot swap<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;\mod{<br>&nbsp;&nbsp;&nbsp;&nbsp;drop drop 0<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;After executing this snipped, "mod" will have two definitions. Calling "mod" will result in the latter being evoked, which simply clears the parameters and returns zero.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Un-defining a Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">/NAME</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;/mod</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;If we execute the code from "Overriding a Subroutine", and then execute this code, "mod" will be left with a single definition: the first one.<br />
|}<br />
<br />
<br />
Any labeled block remains in effect even if the script which defined it finishes execution; make sure to un-define any subroutines you do not wish to linger. The HVM capitalizes on this behavior to limit the number of built-in primitives. The modulus operator described earlier is an example, as is "less than or equal to", which can be defined compositely. Here is a list of all the subroutines the HVM loads by default, with their stack-effect diagrams.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|'''Category''' || '''Subroutine''' || '''Stack Effect Diagram''' || '''Comments'''<br />
|-<br />
| rowspan="2"| Arithmetic || <tt style="font-size:10pt">exp</tt> || <tt style="font-size:10pt">( 1 2 3 -- 1 8 )</tt> || Compute x raised to the y ("exponent")<br />
|-<br />
| <tt style="font-size:10pt">mod</tt> || <tt style="font-size:10pt">( 1 10 4 -- 1 2 )</tt> || Calculate the "modulus"<br />
|-<br />
| rowspan="5"| Logical Operators || <tt style="font-size:10pt">neq</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 1 )</tt> || Logical "Not Equal To"<br />
|-<br />
| <tt style="font-size:10pt">lte</tt> || <tt style="font-size:10pt">( 1 45 45 -- 1 1 )</tt> || Logical "Less Than or Equal To"<br />
|-<br />
| <tt style="font-size:10pt">gt</tt> || <tt style="font-size:10pt">( 1 45 52 -- 1 0 )</tt> || Logical "Greater Than"<br />
|-<br />
| <tt style="font-size:10pt">gte</tt> || <tt style="font-size:10pt">( 1 63 45 -- 1 1 )</tt> || Logical "Greater Than or Equal To"<br />
|}<br />
<br />
===Variables===<br />
<br />
Shuffling the stack beyond the fourth element can get messy. As such, subroutines can allocate ''local variables'' which stay in scope as long as that script is running. Note that variables follow a similar naming convention to subroutines. They are accessed using the @VAR and VAR@ commands. You can remember them easily by putting an imaginary "stack" next to them: <br />
* '''(stack)@VAR''' takes a value from the stack and throws it '''at''' VAR. This is the same as setting VAR = pop().<br />
* '''VAR@(stack)''' takes the value in VAR and throws it '''at''' the stack. This is the same as pushing VAR.<br />
<br />
Here is some sample source for the '''lte''' function, written using variables. Note that, in reality, '''lte''' only uses the stack. This is much more efficient for the HVM, which only initializes the local variable store if it detects your script trying to access it.<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using Variables'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\lte{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Pop the stack twice to get our local variables.<br>&nbsp;&nbsp;&nbsp;&nbsp;#We assume that we are testing @lhs <= @rhs<br>&nbsp;&nbsp;&nbsp;&nbsp;@rhs&nbsp;&nbsp;@lhs<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the first test (<) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res1<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;lt&nbsp;&nbsp;@res1<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our variables, do the second test (==) and<br>&nbsp;&nbsp;&nbsp;&nbsp;# store it in @res2<br>&nbsp;&nbsp;&nbsp;&nbsp;lhs@&nbsp;&nbsp;rhs@&nbsp;&nbsp;eq&nbsp;&nbsp;@res2<br>&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;#Push our two tests and do an or test. Leave it on<br>&nbsp;&nbsp;&nbsp;&nbsp;# the stack to be returned<br>&nbsp;&nbsp;&nbsp;&nbsp;res1@&nbsp;&nbsp;res2@&nbsp;&nbsp;or<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;As an exercise, you should try to convert this to a stack-based alternative. It should end up more concise, and also faster.<br />
|}<br />
<br />
<br />
===Do/If & Loops===<br />
<br />
Most Forth-like languages have an "if" statement, and Henceforth is no exception. However, rather than encapsulating loops into labeled blocks (like Classic Forth), it adds another keyword: "do". This serves two purposes: first, it allows for better inlining than subroutines, and second, it provides a locus of control for jump commands. The second rationale is sometimes written as "otherwise, we'd have to use '''goto'''".<br />
<br />
Inside of a do loop, the commands '''break''' and '''continue''' also take effect; you should read [[HSZ#Id_11:_break | the HSZ documentation]] for an example of how these work conceptually. The mechanism for '''break''' and '''continue''' is different in the HVM, but the effect is the same.<br />
<br />
The standard Hamsterspeak engine also allows breaking out of multiple loops at once. In order to keep the syntax clean in Henceforth, we introduce two bytecodes for that purpose: '''break_x''' and '''continue_x'''. These take the element from the top of the stack, and break out of that many loops. The '''break''' command is effectively equivalent to "1 break_x". <br />
<br />
Thus, we add five more keywords to the list of Henceforth primitives: '''do''', '''if''', '''else''', '''break''', and '''continue'''. Brackets are used to denote code blocks where relevant. We will cover the syntax in simple examples. The first is a user-defined subroutine, "divide_safe", which avoids dividing by zero, and the second is an example of the Fibonacci sequence using "do" to achieve the effect of a "while" loop.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Save Divide-by-Zero Handling'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\divide_safe{<br>&nbsp;&nbsp;&nbsp;&nbsp;#Do a zero-check on the divisor<br>&nbsp;&nbsp;&nbsp;&nbsp;dup&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;#Now, branch on that<br>&nbsp;&nbsp;&nbsp;&nbsp;if{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pop&nbsp;&nbsp;pop&nbsp;&nbsp;0<br>&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;div<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Note that returning zero is just one way of handling the erroneous case. Also, take note of the two "pop" commands; these are required!<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Fibonacci Numbers'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;\fibonacci{<br>&nbsp;&nbsp;&nbsp;&nbsp;@count&nbsp;&nbsp;&nbsp;&nbsp; #Must be >=1<br>&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;@fprev&nbsp;&nbsp;#fib(n-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;@fnow&nbsp;&nbsp; #fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Test-and-decrement<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;0&nbsp;&nbsp;eq<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}else{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;count@&nbsp;&nbsp;-1&nbsp;&nbsp;add&nbsp;&nbsp;@count<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Iterate & Accumulate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fprev@&nbsp;&nbsp;fnow@&nbsp;&nbsp; add<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;fnow@&nbsp;&nbsp; @fprev<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@fnow<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#Re-do the loop<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;continue<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;fnow@<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This subroutine fails given zero as input. Modify it by adding a simple if statement before the main loop. <br />
|}<br />
<br />
This second example is far more interesting; it answers the question "How do I Loop in Henceforth?"<br />
<br />
'''''A Note About Recursion''''': Although Henceforth fully supports recursion, the HVM is not designed for heavily-recursive functions. When possible, please use an iterative solution.<br />
<br />
===HSpeak API Calls & User Scripts===<br />
<br />
The HVM has knowledge of most "built-in" functions that Hamsterspeak provides. These are called like any other subroutine, with one exception: most format converters create specialized subroutine calls out of them. In other words, if I write a call to '''wait_npc''' in my ('''Format T''') code, the converter will create ('''Format B''') source that points ''explicitly'' to the definition of '''wait_npc''' in the HVM; it is '''''not''''' possible to overload '''wait_npc'''. This breaks our promise of fully-interchangeable source formats, but it is hardly noticeable to most users, and keeps our global function lookup table from becoming too cluttered.<br />
<br />
See [[FMF:Plotdict]] for a listing of all API calls and their syntax in Henceforth.<br />
<br />
User-defined scripts are treated exactly like API calls; they are assigned IDs and called explicitly. <br />
<br />
<br />
===Strings===<br />
<br />
Strings in Classic Forth are handled by simply pushing a series of integers (for each letter) to the stack. Eventually, Henceforth may go this route, but for now the API calls to strings are so simple that we chose a much simpler implementation:<br />
* Strings are a series of letters between double-quotes, like "Hello World", "Going?", or "Unicode: \u1000". As hinted by the last example, the HVM supports Unicode.<br />
* When a string is encountered (usually at the top of a script) it is pushed to the '''String Table''' for that script. Strings are pushed in order, cannot be removed or easily manipulated, and have IDs in increasing order.<br />
<br />
This is an ''ad hoc'' implementation of strings, until we get a clearer picture where the standard OHR's development is going with these ideas.<br />
<br />
{| border="1" cellpadding="3" rules="all" width="95%"<br />
| colspan="10" | '''Using a String'''<br />
|-<br />
| valign="top" width="60%" | ''Code''<tt style="font-size:10pt"><br>&nbsp;&nbsp;"Hello world!"<br>&nbsp;&nbsp;0 show_str</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Defining "Hello world!" will place this string at index zero; pushing 0 and then calling the API function '''show_str''' will reference that string explicitly.<br />
|}<br />
<br />
<br />
===Script-Local Subroutines===<br />
<br />
Here's where Henceforth starts to really get picky. When a Hamsterspeak script is converted to Henceforth, it is basically converted into a series of labeled blocks that call each other. Recalling that user-level "scripts" are effectively subroutines, the keen observer will note that a lot of resources are wasted supporting "subroutines" that are, actually, just glorified "goto" blocks. For example, a definition such as "\plus_two{ 2 add }" doesn't need its own stack space --it's just a snippet of code! <br />
<br />
With that in mind, subroutines are allowed to define "script-local" subroutines which operate along a single thread of control within a named block. These are defined with a given ID, and called with an array-like syntax. They cannot be un-defined or overloaded, although theoretically they could be replaced (by re-defining them).<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Defining a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">\[ID]{CODE}</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;\[5]{<br>&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;plus<br>&nbsp;&nbsp;&nbsp;&nbsp;div mult sub<br>&nbsp;&nbsp;}</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;Our "plus two" example from before, assigned to index 5.<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="85%"<br />
| colspan="10" | '''Calling a Script-Local Subroutine'''<br />
|-<br />
| valign="top" | ''Syntax''<br>&nbsp;&nbsp;<tt style="font-size:10pt">[ID]</tt> || valign="top" width="250" | ''Example''<br><tt style="font-size:10pt">&nbsp;&nbsp;16&nbsp;&nbsp;[5]</tt> || valign="top" | ''Comments''<br>&nbsp;&nbsp;This pushes 16, and calls local block 5. The result is a stack containing the number 18, if we define [5] as listed above.<br />
|}<br />
<br />
<br />
===Local Parameters & Return Values===<br />
<br />
An astute reader will have noticed that scripts currently have no way of passing values to other scripts. Calling "5 6 my_func" won't work, because '''my_func''' will get its own independent stack. This problem is circumvented through the use of local parameters, which also provide a slight speedup for small, frequently called subroutines. The local parameters are accessed by ID, with -1 a special ID that refers to the script's return value. <br />
* Executing @[5] will "pop the (parent) stack to local parameter 5"<br />
* Executing [5]@ will "push local parameter 5 onto the (current) stack<br />
For the return value, it's backwards:<br />
* Executing @[-1] will "pop the (current) stack to the return value"<br />
* Executing [-1]@ will "push the return value onto the (parent) stack", '''''unless''''' the return value is set to ''invalid'' (INT_MIN, for now).<br />
The only point of interest is that last bit. The problem is, the HSpeak command "return" sets the return value, and "exitscript" will exit and return that value. Some scripts simply aren't intended to use their return values, which would leave an extra element on the stack. We could always just pop the return value, but we don't want to punish a highly modularized script with an un-necessary drag on performance. This is the alternative we came up with.<br />
<br />
===Comments===<br />
<br />
Henceforth supports comments, although it's important to realize that Formats B and HF will drop your comments; they can't be encoded. A comment begins with a hash mark (#) and ends with a line feed. <br />
<br />
<br />
===Global Variables===<br />
<br />
All that's left are global variables, which are manipulated with syntax like '''@[65.G]''' and '''[65.G]@'''. We realize this is a bit awkward, and we're open to a cleaner syntax (once we actually get a release going).<br />
<br />
<br />
<br />
==The Three Formats of Henceforth==<br />
<br />
As mentioned, Henceforth source can appear in three different formats: T, B, and HF. Here's a side-by-side comparison of the three.<br />
<br />
{|<br />
| &nbsp; || '''Type''' || '''Use'''<br />
|-<br />
| Format T || text || All human interaction (coding, discussing ideas, etc.) occurs in this format.<br />
|-<br />
| Format B || bytecode (binary) || The HVM executes Format B code. The HSP2HF cross-compiler outputs this format.<br />
|-<br />
| Format HF || compressed || The XRPG specification requires all Format B code to be compressed into format HF lumps, which are then stored locally on the user's phone.<br />
|}<br />
<br />
<br />
===HFB===<br />
[[FMF:HFB]]<br />
<br />
= The Cross-Compiler =<br />
<br />
[[HSP2HF]]<br />
<br />
=Final Considerations=<br />
<br />
(later)<br />
<br />
<br />
<br />
<br />
<br />
<br />
{{Incomplete}}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=HSP2HF&diff=18572
HSP2HF
2008-12-30T00:12:54Z
<p>Sorlok reaves: /* Inlining removes the need for and/or primitives */</p>
<hr />
<div>{{FMF}}<br />
<br />
Most of you will never have to touch Henceforth if you don't want to. (But who wouldn't want to?) The HSP2HF cross-compiler is used to automatically convert your HamsterSpeak into Henceforth bytecode. It is part of the RPG2XRPG conversion process.<br />
<br />
<br />
==How the Cross-Compiler Works: Motivations==<br />
<br />
Some parts of the OHRRPGCE FMF project are slightly incompatible with the standard OHRRPGCE, but for scripting this is simply unacceptable. Unlike things like, say, a slightly jittery enemy HP value -which is immediately apparent when you first load your game on a phone- a silent error in a script is worth hours of headache-inducing debugging, and probably not worth anything at all in the end. <br />
<br />
So, the goal of the cross-compiler is script-level compatibility. Efficiency and conciseness, while important, take a backseat to this driving need.<br />
<br />
<br />
==How the Cross-Compiler Works: Naive Compiler==<br />
<br />
The Henceforth cross-compiler benefits from HamsterSpeak's tree-like structure; a naive conversion can simply convert each node to a local Henceforth function. Consider the following script (from Wandering Hamster, loaded in the HSP2HF utility)<br />
<br />
[[Image:FMF_cross_compiler_screenshot.png]]<br />
<br />
Typical to most HSZ scripts, "setnpcspeed" contains a head "do" node. This node happens to contain only one element, which takes three arguments, each of which is a simple number or variable. Clicking "cross-compile" will invoke the naive converter, which produces the following code:<br />
<br />
\[14]{<br />
[1]@<br />
}<br />
\[12]{<br />
3<br />
}<br />
\[10]{<br />
[0]@<br />
}<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
<br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[4]()<br />
do_end<br />
<br />
Let's start from the local variables section:<br />
@[1]<br />
This is a shorthand syntax; it basically calls "local store", a function deep within the script interpreter which does something like this:<br />
void localStore(int arg) {<br />
local_variables[arg] = pop_parent();<br />
}<br />
<br />
Next, we have the main loop. The "do_start" and "do_end" primitives are there to help the "break" and "continue" primitives to function properly. The meat of the main loop is the call to:<br />
[4]()<br />
...which is simply a call to a script-local subroutine.<br />
<br />
The following code defines script-local subroutine 4:<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
...as three script-local calls([10], [12], and [14]) and one built-in function call ('''H'''amster'''S'''peak:78, alterNPC). The remaining local functions are equally easy to understand. For example, "[1]@" calls "local load" with "1" as an argument. Local load is defined internally as:<br />
void localLoad(arg1) {<br />
push(local_variables[arg1]);<br />
}<br />
<br />
==How the Cross-Compiler Works: Reasonable Inlining==<br />
<br />
Simple functions like "setnpcspeed" are very easy to inline, just by copying the leaf nodes' source into their parents. The previous script can be re-written as: <br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[0]@<br />
3<br />
[1]@<br />
[HS:78]()<br />
do_end<br />
...which is much more concise. Due to the nature of HF bytecode, inlining usually improves both performance and storage efficiency. (When I have time to profile, I hope to collect some facts to back this up.) However, inlining everything is often either impossible or unwise, which is why one needs a policy for inlining. At the moment, the OHRRPGCE FMF's cross-compiler uses the following algorithm to determine what to inline:<br />
1) Start by doing a naive conversion, and retain the tree structure. <br />
Mark all nodes as "not dead" and with a "count" of 1.<br />
2) Loop until all nodes are inlined or dead. At each iteration, do the following.<br />
3) Determine which nodes are leaf nodes. (Leaf nodes have ''no'' children, or only dead children).<br />
a) If this node cannot be inlined (e.g., it's self-referential, or checks b/c/d/e below fail), <br />
mark it as "dead".<br />
b) If this node's count is "1", inline it. (Copy its corresponding source into any node that <br />
references it, incrementing their "count" value by 1, and then delete it.)<br />
c) If this node's count is "2", and it is referenced 8 times or less, inline it.<br />
d) If this node's count is "3", and it is referenced 4 times or less, inline it.<br />
e) If this node's count is "4" or more, only inline it if it is referenced exactly once.<br />
<br />
We are still discussing what makes a node impossible to inline. Technically, the problem is difficult, but Hamsterspeak byte-code is fairly structured in nature, which means we can probably define a few simple criteria for exclusion. <br />
<br />
<br />
==Primitives & HSPEAK->HF Snippits==<br />
<br />
The cross-compiler inserts snippets of HF code when it encounters an HSPEAK node of a given type. For example, at node 10, given a "number" node with value '''75''', it inserts:<br />
<br />
\[10] {<br />
75<br />
}<br />
<br />
Henceforth, we shall refer to a node of ID n as: '''\[n] {}''' --this allows us to generalize HSPEAK nodes into simple templates. Just a reminder: '''\[n] {}''' represents a ''script-local subroutine''; it is ''not'' valid '''Format T''' syntax.<br />
<br />
The following templates are loaded when the HVM initializes, so the cross-compiler makes use of them to simplify syntax:<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" |'''''Templates'''''<br />
|-<br />
| valign="top" | <span style="color:#0000AA;font-weight:bold">#Template: -2 4 set_var will set local variable 1 to value 4</span><br>\set_var {<br>&nbsp;&nbsp;swap<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;@[]<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;@[.G]<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br><span style="color:#0000AA;font-weight:bold">#Template: 2 get_var will return the contents of global variable 2</span><br>\get_var {<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;[]@<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;[.G]@<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
Here are the snippets used by the cross-compiler; we repeat numbers for the sake of completeness:<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Numbers'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">number</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Do Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | do<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''If Statements'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | if<br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">then_y</span><br><span style="color:green;font-weight:bold">else_z</span><br />
| valign="top" | <br>\[n]{<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[z]()</span><br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Then/Else Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | then/else<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;...<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Break/Continue'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">command</span><br />
| valign="top" | <span style="color:green;font-weight:bold">amount</span><br>''if amount==1 then:''<br>&nbsp;&nbsp;''skip amount''<br>''else:''<br>&nbsp;&nbsp;''append "_x" to command''<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[amount]</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">command</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Returning'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">return</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;''invalid''<br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''While/For'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">while</span><br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;not if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">for</span><br />
| valign="top" | <span style="color:green;font-weight:bold">count_id_x</span><br><span style="color:green;font-weight:bold">counter_start_s</span><br><span style="color:green;font-weight:bold">count_end_e</span><br><span style="color:green;font-weight:bold">counter_step_w</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[s]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;set_var<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;0 gt<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;gt xor not<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;neq and<br>&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;add<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| colspan="4" | '''''Note 1:''''' ''The block <span style="color:green">inline_y</span> simply unrolls the <span style="color:green">do_y</span> block into the body of '''[n]()'''. This is done so that '''break''' and '''continue''' will function properly.'' <br>'''''Note 2:''''' ''The upshot of this is that <span style="color:green">do_y</span> will be instantly culled from the source, unless another node references it (which would be a bit of a hack, in my opinion. Regardless, this is fine, and will not affect program validity in any way.''<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Switch'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | switch<br />
| valign="top" | ???<br />
| valign="top" | ''This is not yet documented in [[HSZ]], so we will deal with it later.<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Variable Access'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">global</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[.G]@<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">local</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[]@<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Math Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">set_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">increment_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"></span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;add<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">decrement_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"></span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;sub<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">not</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;not<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">and</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;[r]()<br>&nbsp;&nbsp;} else { <br>&nbsp;&nbsp;&nbsp;&nbsp;False<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">or</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;not<br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;[r]()<br>&nbsp;&nbsp;} else { <br>&nbsp;&nbsp;&nbsp;&nbsp;True<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">operand</span><br>''If the operand is listed <br>above, use '''that''' code <br>block, not this one.''<br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">operand</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Built-In and User-Defined Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">built-in</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;hspeak_api_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">user-script</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;user_script_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=HSP2HF&diff=18571
HSP2HF
2008-12-29T19:14:25Z
<p>Sorlok reaves: /* Refactored templates into their own section. */</p>
<hr />
<div>{{FMF}}<br />
<br />
Most of you will never have to touch Henceforth if you don't want to. (But who wouldn't want to?) The HSP2HF cross-compiler is used to automatically convert your HamsterSpeak into Henceforth bytecode. It is part of the RPG2XRPG conversion process.<br />
<br />
<br />
==How the Cross-Compiler Works: Motivations==<br />
<br />
Some parts of the OHRRPGCE FMF project are slightly incompatible with the standard OHRRPGCE, but for scripting this is simply unacceptable. Unlike things like, say, a slightly jittery enemy HP value -which is immediately apparent when you first load your game on a phone- a silent error in a script is worth hours of headache-inducing debugging, and probably not worth anything at all in the end. <br />
<br />
So, the goal of the cross-compiler is script-level compatibility. Efficiency and conciseness, while important, take a backseat to this driving need.<br />
<br />
<br />
==How the Cross-Compiler Works: Naive Compiler==<br />
<br />
The Henceforth cross-compiler benefits from HamsterSpeak's tree-like structure; a naive conversion can simply convert each node to a local Henceforth function. Consider the following script (from Wandering Hamster, loaded in the HSP2HF utility)<br />
<br />
[[Image:FMF_cross_compiler_screenshot.png]]<br />
<br />
Typical to most HSZ scripts, "setnpcspeed" contains a head "do" node. This node happens to contain only one element, which takes three arguments, each of which is a simple number or variable. Clicking "cross-compile" will invoke the naive converter, which produces the following code:<br />
<br />
\[14]{<br />
[1]@<br />
}<br />
\[12]{<br />
3<br />
}<br />
\[10]{<br />
[0]@<br />
}<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
<br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[4]()<br />
do_end<br />
<br />
Let's start from the local variables section:<br />
@[1]<br />
This is a shorthand syntax; it basically calls "local store", a function deep within the script interpreter which does something like this:<br />
void localStore(int arg) {<br />
local_variables[arg] = pop_parent();<br />
}<br />
<br />
Next, we have the main loop. The "do_start" and "do_end" primitives are there to help the "break" and "continue" primitives to function properly. The meat of the main loop is the call to:<br />
[4]()<br />
...which is simply a call to a script-local subroutine.<br />
<br />
The following code defines script-local subroutine 4:<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
...as three script-local calls([10], [12], and [14]) and one built-in function call ('''H'''amster'''S'''peak:78, alterNPC). The remaining local functions are equally easy to understand. For example, "[1]@" calls "local load" with "1" as an argument. Local load is defined internally as:<br />
void localLoad(arg1) {<br />
push(local_variables[arg1]);<br />
}<br />
<br />
==How the Cross-Compiler Works: Reasonable Inlining==<br />
<br />
Simple functions like "setnpcspeed" are very easy to inline, just by copying the leaf nodes' source into their parents. The previous script can be re-written as: <br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[0]@<br />
3<br />
[1]@<br />
[HS:78]()<br />
do_end<br />
...which is much more concise. Due to the nature of HF bytecode, inlining usually improves both performance and storage efficiency. (When I have time to profile, I hope to collect some facts to back this up.) However, inlining everything is often either impossible or unwise, which is why one needs a policy for inlining. At the moment, the OHRRPGCE FMF's cross-compiler uses the following algorithm to determine what to inline:<br />
1) Start by doing a naive conversion, and retain the tree structure. <br />
Mark all nodes as "not dead" and with a "count" of 1.<br />
2) Loop until all nodes are inlined or dead. At each iteration, do the following.<br />
3) Determine which nodes are leaf nodes. (Leaf nodes have ''no'' children, or only dead children).<br />
a) If this node cannot be inlined (e.g., it's self-referential, or checks b/c/d/e below fail), <br />
mark it as "dead".<br />
b) If this node's count is "1", inline it. (Copy its corresponding source into any node that <br />
references it, incrementing their "count" value by 1, and then delete it.)<br />
c) If this node's count is "2", and it is referenced 8 times or less, inline it.<br />
d) If this node's count is "3", and it is referenced 4 times or less, inline it.<br />
e) If this node's count is "4" or more, only inline it if it is referenced exactly once.<br />
<br />
We are still discussing what makes a node impossible to inline. Technically, the problem is difficult, but Hamsterspeak byte-code is fairly structured in nature, which means we can probably define a few simple criteria for exclusion. <br />
<br />
<br />
==Primitives & HSPEAK->HF Snippits==<br />
<br />
The cross-compiler inserts snippets of HF code when it encounters an HSPEAK node of a given type. For example, at node 10, given a "number" node with value '''75''', it inserts:<br />
<br />
\[10] {<br />
75<br />
}<br />
<br />
Henceforth, we shall refer to a node of ID n as: '''\[n] {}''' --this allows us to generalize HSPEAK nodes into simple templates. Just a reminder: '''\[n] {}''' represents a ''script-local subroutine''; it is ''not'' valid '''Format T''' syntax.<br />
<br />
The following templates are loaded when the HVM initializes, so the cross-compiler makes use of them to simplify syntax:<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" |'''''Templates'''''<br />
|-<br />
| valign="top" | <span style="color:#0000AA;font-weight:bold">#Template: -2 4 set_var will set local variable 1 to value 4</span><br>\set_var {<br>&nbsp;&nbsp;swap<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;@[]<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;@[.G]<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br><span style="color:#0000AA;font-weight:bold">#Template: 2 get_var will return the contents of global variable 2</span><br>\get_var {<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;[]@<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;[.G]@<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
Here are the snippets used by the cross-compiler; we repeat numbers for the sake of completeness:<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Numbers'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">number</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Do Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | do<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''If Statements'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | if<br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">then_y</span><br><span style="color:green;font-weight:bold">else_z</span><br />
| valign="top" | <br>\[n]{<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[z]()</span><br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Then/Else Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | then/else<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;...<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Break/Continue'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">command</span><br />
| valign="top" | <span style="color:green;font-weight:bold">amount</span><br>''if amount==1 then:''<br>&nbsp;&nbsp;''skip amount''<br>''else:''<br>&nbsp;&nbsp;''append "_x" to command''<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[amount]</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">command</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Returning'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">return</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;''invalid''<br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''While/For'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">while</span><br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;not if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">for</span><br />
| valign="top" | <span style="color:green;font-weight:bold">count_id_x</span><br><span style="color:green;font-weight:bold">counter_start_s</span><br><span style="color:green;font-weight:bold">count_end_e</span><br><span style="color:green;font-weight:bold">counter_step_w</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[s]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;set_var<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;0 gt<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;gt xor not<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;neq and<br>&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;add<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| colspan="4" | '''''Note 1:''''' ''The block <span style="color:green">inline_y</span> simply unrolls the <span style="color:green">do_y</span> block into the body of '''[n]()'''. This is done so that '''break''' and '''continue''' will function properly.'' <br>'''''Note 2:''''' ''The upshot of this is that <span style="color:green">do_y</span> will be instantly culled from the source, unless another node references it (which would be a bit of a hack, in my opinion. Regardless, this is fine, and will not affect program validity in any way.''<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Switch'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | switch<br />
| valign="top" | ???<br />
| valign="top" | ''This is not yet documented in [[HSZ]], so we will deal with it later.<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Variable Access'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">global</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[.G]@<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">local</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[]@<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Math Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">set_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">increment_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"></span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;add<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">decrement_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"></span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;sub<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"></span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">not</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;not<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">operand</span><br>''If the operand is listed <br>above, use '''that''' code <br>block, not this one.''<br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">operand</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Built-In and User-Defined Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">built-in</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;hspeak_api_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">user-script</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;user_script_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=HSP2HF&diff=18570
HSP2HF
2008-12-29T19:07:33Z
<p>Sorlok reaves: /* Note. */</p>
<hr />
<div>{{FMF}}<br />
<br />
Most of you will never have to touch Henceforth if you don't want to. (But who wouldn't want to?) The HSP2HF cross-compiler is used to automatically convert your HamsterSpeak into Henceforth bytecode. It is part of the RPG2XRPG conversion process.<br />
<br />
<br />
==How the Cross-Compiler Works: Motivations==<br />
<br />
Some parts of the OHRRPGCE FMF project are slightly incompatible with the standard OHRRPGCE, but for scripting this is simply unacceptable. Unlike things like, say, a slightly jittery enemy HP value -which is immediately apparent when you first load your game on a phone- a silent error in a script is worth hours of headache-inducing debugging, and probably not worth anything at all in the end. <br />
<br />
So, the goal of the cross-compiler is script-level compatibility. Efficiency and conciseness, while important, take a backseat to this driving need.<br />
<br />
<br />
==How the Cross-Compiler Works: Naive Compiler==<br />
<br />
The Henceforth cross-compiler benefits from HamsterSpeak's tree-like structure; a naive conversion can simply convert each node to a local Henceforth function. Consider the following script (from Wandering Hamster, loaded in the HSP2HF utility)<br />
<br />
[[Image:FMF_cross_compiler_screenshot.png]]<br />
<br />
Typical to most HSZ scripts, "setnpcspeed" contains a head "do" node. This node happens to contain only one element, which takes three arguments, each of which is a simple number or variable. Clicking "cross-compile" will invoke the naive converter, which produces the following code:<br />
<br />
\[14]{<br />
[1]@<br />
}<br />
\[12]{<br />
3<br />
}<br />
\[10]{<br />
[0]@<br />
}<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
<br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[4]()<br />
do_end<br />
<br />
Let's start from the local variables section:<br />
@[1]<br />
This is a shorthand syntax; it basically calls "local store", a function deep within the script interpreter which does something like this:<br />
void localStore(int arg) {<br />
local_variables[arg] = pop_parent();<br />
}<br />
<br />
Next, we have the main loop. The "do_start" and "do_end" primitives are there to help the "break" and "continue" primitives to function properly. The meat of the main loop is the call to:<br />
[4]()<br />
...which is simply a call to a script-local subroutine.<br />
<br />
The following code defines script-local subroutine 4:<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
...as three script-local calls([10], [12], and [14]) and one built-in function call ('''H'''amster'''S'''peak:78, alterNPC). The remaining local functions are equally easy to understand. For example, "[1]@" calls "local load" with "1" as an argument. Local load is defined internally as:<br />
void localLoad(arg1) {<br />
push(local_variables[arg1]);<br />
}<br />
<br />
==How the Cross-Compiler Works: Reasonable Inlining==<br />
<br />
Simple functions like "setnpcspeed" are very easy to inline, just by copying the leaf nodes' source into their parents. The previous script can be re-written as: <br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[0]@<br />
3<br />
[1]@<br />
[HS:78]()<br />
do_end<br />
...which is much more concise. Due to the nature of HF bytecode, inlining usually improves both performance and storage efficiency. (When I have time to profile, I hope to collect some facts to back this up.) However, inlining everything is often either impossible or unwise, which is why one needs a policy for inlining. At the moment, the OHRRPGCE FMF's cross-compiler uses the following algorithm to determine what to inline:<br />
1) Start by doing a naive conversion, and retain the tree structure. <br />
Mark all nodes as "not dead" and with a "count" of 1.<br />
2) Loop until all nodes are inlined or dead. At each iteration, do the following.<br />
3) Determine which nodes are leaf nodes. (Leaf nodes have ''no'' children, or only dead children).<br />
a) If this node cannot be inlined (e.g., it's self-referential, or checks b/c/d/e below fail), <br />
mark it as "dead".<br />
b) If this node's count is "1", inline it. (Copy its corresponding source into any node that <br />
references it, incrementing their "count" value by 1, and then delete it.)<br />
c) If this node's count is "2", and it is referenced 8 times or less, inline it.<br />
d) If this node's count is "3", and it is referenced 4 times or less, inline it.<br />
e) If this node's count is "4" or more, only inline it if it is referenced exactly once.<br />
<br />
We are still discussing what makes a node impossible to inline. Technically, the problem is difficult, but Hamsterspeak byte-code is fairly structured in nature, which means we can probably define a few simple criteria for exclusion. <br />
<br />
<br />
==Primitives & HSPEAK->HF Snippits==<br />
<br />
The cross-compiler inserts snippets of HF code when it encounters an HSPEAK node of a given type. For example, at node 10, given a "number" node with value '''75''', it inserts:<br />
<br />
\[10] {<br />
75<br />
}<br />
<br />
Henceforth, we shall refer to a node of ID n as: '''\[n] {}''' --this allows us to generalize HSPEAK nodes into simple templates. Just a reminder: '''\[n] {}''' represents a ''script-local subroutine''; it is ''not'' valid '''Format T''' syntax.<br />
<br />
Here are the snippets used by the cross-compiler; we repeat numbers for the sake of completeness:<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Numbers'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">number</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Do Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | do<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''If Statements'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | if<br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">then_y</span><br><span style="color:green;font-weight:bold">else_z</span><br />
| valign="top" | <br>\[n]{<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[z]()</span><br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Then/Else Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | then/else<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;...<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Break/Continue'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">command</span><br />
| valign="top" | <span style="color:green;font-weight:bold">amount</span><br>''if amount==1 then:''<br>&nbsp;&nbsp;''skip amount''<br>''else:''<br>&nbsp;&nbsp;''append "_x" to command''<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[amount]</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">command</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Returning'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">return</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;''invalid''<br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''While/For'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">while</span><br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;not if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">for</span><br />
| valign="top" | <span style="color:green;font-weight:bold">count_id_x</span><br><span style="color:green;font-weight:bold">counter_start_s</span><br><span style="color:green;font-weight:bold">count_end_e</span><br><span style="color:green;font-weight:bold">counter_step_w</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | <span style="color:#0000AA;font-weight:bold">#Template: -2 4 set_var will set local variable 1 to value 4</span><br>\set_var {<br>&nbsp;&nbsp;swap<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;@[]<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;@[.G]<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br><span style="color:#0000AA;font-weight:bold">#Template: 2 get_var will return the contents of global variable 2</span><br>\get_var {<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;[]@<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;[.G]@<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br>\[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[s]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;set_var<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;0 gt<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;gt xor not<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;neq and<br>&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;add<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| colspan="4" | '''''Note 1:''''' ''The block <span style="color:green">inline_y</span> simply unrolls the <span style="color:green">do_y</span> block into the body of '''[n]()'''. This is done so that '''break''' and '''continue''' will function properly.'' <br>'''''Note 2:''''' ''The upshot of this is that <span style="color:green">do_y</span> will be instantly culled from the source, unless another node references it (which would be a bit of a hack, in my opinion. Regardless, this is fine, and will not affect program validity in any way.''<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Switch'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | switch<br />
| valign="top" | ???<br />
| valign="top" | ''This is not yet documented in [[HSZ]], so we will deal with it later.<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Variable Access'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">global</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[.G]@<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">local</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[]@<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Math Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">set_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">increment_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;add<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">decrement_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;sub<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">not</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;not<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">operand</span><br>''If the operand is listed <br>above, use '''that''' code <br>block, not this one.''<br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">operand</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Built-In and User-Defined Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">built-in</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;hspeak_api_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">user-script</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;user_script_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=HSP2HF&diff=18569
HSP2HF
2008-12-29T19:03:56Z
<p>Sorlok reaves: /* Missed a few @'s */</p>
<hr />
<div>{{FMF}}<br />
<br />
Most of you will never have to touch Henceforth if you don't want to. (But who wouldn't want to?) The HSP2HF cross-compiler is used to automatically convert your HamsterSpeak into Henceforth bytecode. It is part of the RPG2XRPG conversion process.<br />
<br />
<br />
==How the Cross-Compiler Works: Motivations==<br />
<br />
Some parts of the OHRRPGCE FMF project are slightly incompatible with the standard OHRRPGCE, but for scripting this is simply unacceptable. Unlike things like, say, a slightly jittery enemy HP value -which is immediately apparent when you first load your game on a phone- a silent error in a script is worth hours of headache-inducing debugging, and probably not worth anything at all in the end. <br />
<br />
So, the goal of the cross-compiler is script-level compatibility. Efficiency and conciseness, while important, take a backseat to this driving need.<br />
<br />
<br />
==How the Cross-Compiler Works: Naive Compiler==<br />
<br />
The Henceforth cross-compiler benefits from HamsterSpeak's tree-like structure; a naive conversion can simply convert each node to a local Henceforth function. Consider the following script (from Wandering Hamster, loaded in the HSP2HF utility)<br />
<br />
[[Image:FMF_cross_compiler_screenshot.png]]<br />
<br />
Typical to most HSZ scripts, "setnpcspeed" contains a head "do" node. This node happens to contain only one element, which takes three arguments, each of which is a simple number or variable. Clicking "cross-compile" will invoke the naive converter, which produces the following code:<br />
<br />
\[14]{<br />
[1]@<br />
}<br />
\[12]{<br />
3<br />
}<br />
\[10]{<br />
[0]@<br />
}<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
<br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[4]()<br />
do_end<br />
<br />
Let's start from the local variables section:<br />
@[1]<br />
This is a shorthand syntax; it basically calls "local store", a function deep within the script interpreter which does something like this:<br />
void localStore(int arg) {<br />
local_variables[arg] = pop_parent();<br />
}<br />
<br />
Next, we have the main loop. The "do_start" and "do_end" primitives are there to help the "break" and "continue" primitives to function properly. The meat of the main loop is the call to:<br />
[4]()<br />
...which is simply a call to a script-local subroutine.<br />
<br />
The following code defines script-local subroutine 4:<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
...as three script-local calls([10], [12], and [14]) and one built-in function call ('''H'''amster'''S'''peak:78, alterNPC). The remaining local functions are equally easy to understand. For example, "[1]@" calls "local load" with "1" as an argument. Local load is defined internally as:<br />
void localLoad(arg1) {<br />
push(local_variables[arg1]);<br />
}<br />
<br />
==How the Cross-Compiler Works: Reasonable Inlining==<br />
<br />
Simple functions like "setnpcspeed" are very easy to inline, just by copying the leaf nodes' source into their parents. The previous script can be re-written as: <br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[0]@<br />
3<br />
[1]@<br />
[HS:78]()<br />
do_end<br />
...which is much more concise. Due to the nature of HF bytecode, inlining usually improves both performance and storage efficiency. (When I have time to profile, I hope to collect some facts to back this up.) However, inlining everything is often either impossible or unwise, which is why one needs a policy for inlining. At the moment, the OHRRPGCE FMF's cross-compiler uses the following algorithm to determine what to inline:<br />
1) Start by doing a naive conversion, and retain the tree structure. <br />
Mark all nodes as "not dead" and with a "count" of 1.<br />
2) Loop until all nodes are inlined or dead. At each iteration, do the following.<br />
3) Determine which nodes are leaf nodes. (Leaf nodes have ''no'' children, or only dead children).<br />
a) If this node cannot be inlined (e.g., it's self-referential, or checks b/c/d/e below fail), <br />
mark it as "dead".<br />
b) If this node's count is "1", inline it. (Copy its corresponding source into any node that <br />
references it, incrementing their "count" value by 1, and then delete it.)<br />
c) If this node's count is "2", and it is referenced 8 times or less, inline it.<br />
d) If this node's count is "3", and it is referenced 4 times or less, inline it.<br />
e) If this node's count is "4" or more, only inline it if it is referenced exactly once.<br />
<br />
We are still discussing what makes a node impossible to inline. Technically, the problem is difficult, but Hamsterspeak byte-code is fairly structured in nature, which means we can probably define a few simple criteria for exclusion. <br />
<br />
<br />
==Primitives & HSPEAK->HF Snippits==<br />
<br />
The cross-compiler inserts snippets of HF code when it encounters an HSPEAK node of a given type. For example, at node 10, given a "number" node with value '''75''', it inserts:<br />
<br />
\[10] {<br />
75<br />
}<br />
<br />
Henceforth, we shall refer to a node of ID n as: '''\[n] {}''' --this allows us to generalize HSPEAK nodes into simple templates. Here are the snippets used by the cross-compiler; we repeat numbers for the sake of completeness:<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Numbers'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">number</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Do Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | do<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''If Statements'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | if<br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">then_y</span><br><span style="color:green;font-weight:bold">else_z</span><br />
| valign="top" | <br>\[n]{<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[z]()</span><br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Then/Else Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | then/else<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;...<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Break/Continue'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">command</span><br />
| valign="top" | <span style="color:green;font-weight:bold">amount</span><br>''if amount==1 then:''<br>&nbsp;&nbsp;''skip amount''<br>''else:''<br>&nbsp;&nbsp;''append "_x" to command''<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[amount]</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">command</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Returning'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">return</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;''invalid''<br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''While/For'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">while</span><br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;not if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">for</span><br />
| valign="top" | <span style="color:green;font-weight:bold">count_id_x</span><br><span style="color:green;font-weight:bold">counter_start_s</span><br><span style="color:green;font-weight:bold">count_end_e</span><br><span style="color:green;font-weight:bold">counter_step_w</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | <span style="color:#0000AA;font-weight:bold">#Template: -2 4 set_var will set local variable 1 to value 4</span><br>\set_var {<br>&nbsp;&nbsp;swap<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;@[]<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;@[.G]<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br><span style="color:#0000AA;font-weight:bold">#Template: 2 get_var will return the contents of global variable 2</span><br>\get_var {<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;[]@<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;[.G]@<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br>\[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[s]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;set_var<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;0 gt<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;gt xor not<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;neq and<br>&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;add<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| colspan="4" | '''''Note 1:''''' ''The block <span style="color:green">inline_y</span> simply unrolls the <span style="color:green">do_y</span> block into the body of '''[n]()'''. This is done so that '''break''' and '''continue''' will function properly.'' <br>'''''Note 2:''''' ''The upshot of this is that <span style="color:green">do_y</span> will be instantly culled from the source, unless another node references it (which would be a bit of a hack, in my opinion. Regardless, this is fine, and will not affect program validity in any way.''<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Switch'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | switch<br />
| valign="top" | ???<br />
| valign="top" | ''This is not yet documented in [[HSZ]], so we will deal with it later.<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Variable Access'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">global</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[.G]@<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">local</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[]@<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Math Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">set_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">increment_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;add<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">decrement_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;sub<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">not</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;not<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">operand</span><br>''If the operand is listed <br>above, use '''that''' code <br>block, not this one.''<br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">operand</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Built-In and User-Defined Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">built-in</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;hspeak_api_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">user-script</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;user_script_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=HSP2HF&diff=18561
HSP2HF
2008-12-21T02:31:30Z
<p>Sorlok reaves: /* Further syntax clean-up */</p>
<hr />
<div>{{FMF}}<br />
<br />
Most of you will never have to touch Henceforth if you don't want to. (But who wouldn't want to?) The HSP2HF cross-compiler is used to automatically convert your HamsterSpeak into Henceforth bytecode. It is part of the RPG2XRPG conversion process.<br />
<br />
<br />
==How the Cross-Compiler Works: Motivations==<br />
<br />
Some parts of the OHRRPGCE FMF project are slightly incompatible with the standard OHRRPGCE, but for scripting this is simply unacceptable. Unlike things like, say, a slightly jittery enemy HP value -which is immediately apparent when you first load your game on a phone- a silent error in a script is worth hours of headache-inducing debugging, and probably not worth anything at all in the end. <br />
<br />
So, the goal of the cross-compiler is script-level compatibility. Efficiency and conciseness, while important, take a backseat to this driving need.<br />
<br />
<br />
==How the Cross-Compiler Works: Naive Compiler==<br />
<br />
The Henceforth cross-compiler benefits from HamsterSpeak's tree-like structure; a naive conversion can simply convert each node to a local Henceforth function. Consider the following script (from Wandering Hamster, loaded in the HSP2HF utility)<br />
<br />
[[Image:FMF_cross_compiler_screenshot.png]]<br />
<br />
Typical to most HSZ scripts, "setnpcspeed" contains a head "do" node. This node happens to contain only one element, which takes three arguments, each of which is a simple number or variable. Clicking "cross-compile" will invoke the naive converter, which produces the following code:<br />
<br />
\[14]{<br />
1<br />
L[]@<br />
}<br />
\[12]{<br />
3<br />
}<br />
\[10]{<br />
0<br />
L[]@<br />
}<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
<br />
#Init local variables<br />
@L[1]<br />
@L[0]<br />
<br />
#Main script loop<br />
do_start<br />
[4]()<br />
do_end<br />
<br />
Let's start from the local variables section:<br />
@L[1]<br />
This is a shorthand syntax; it basically calls "local store", a function deep within the script interpreter which does something like this:<br />
void localStore(int arg) {<br />
local_variables[arg] = pop_parent();<br />
}<br />
<br />
Next, we have the main loop. The "do_start" and "do_end" primitives are there to help the "break" and "continue" primitives to function properly. The meat of the main loop is the call to:<br />
[4]()<br />
...which is simply a call to a script-local subroutine.<br />
<br />
The following code defines script-local subroutine 4:<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
...as three script-local calls([10], [12], and [14]) and one built-in function call ('''H'''amster'''S'''peak:78, alterNPC). The remaining local functions are equally easy to understand. For example, "[1]@" calls "local load" with "1" as an argument. Local load is defined internally as:<br />
void localLoad(arg1) {<br />
push(local_variables[arg1]);<br />
}<br />
<br />
<br />
==How the Cross-Compiler Works: Reasonable Inlining==<br />
<br />
Simple functions like "setnpcspeed" are very easy to inline, just by copying the leaf nodes' source into their parents. The previous script can be re-written as: <br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[0]@<br />
3<br />
[1]@<br />
[HS:78]()<br />
do_end<br />
...which is much more concise. Due to the nature of HF bytecode, inlining usually improves both performance and storage efficiency. (When I have time to profile, I hope to collect some facts to back this up.) However, inlining everything is often either impossible or unwise, which is why one needs a policy for inlining. At the moment, the OHRRPGCE FMF's cross-compiler uses the following algorithm to determine what to inline:<br />
1) Start by doing a naive conversion, and retain the tree structure. <br />
Mark all nodes as "not dead" and with a "count" of 1.<br />
2) Loop until all nodes are inlined or dead. At each iteration, do the following.<br />
3) Determine which nodes are leaf nodes. (Leaf nodes have ''no'' children, or only dead children).<br />
a) If this node cannot be inlined (e.g., it's self-referential, or checks b/c/d/e below fail), <br />
mark it as "dead".<br />
b) If this node's count is "1", inline it. (Copy its corresponding source into any node that <br />
references it, incrementing their "count" value by 1, and then delete it.)<br />
c) If this node's count is "2", and it is referenced 8 times or less, inline it.<br />
d) If this node's count is "3", and it is referenced 4 times or less, inline it.<br />
e) If this node's count is "4" or more, only inline it if it is referenced exactly once.<br />
<br />
We are still discussing what makes a node impossible to inline. Technically, the problem is difficult, but Hamsterspeak byte-code is fairly structured in nature, which means we can probably define a few simple criteria for exclusion. <br />
<br />
<br />
==Primitives & HSPEAK->HF Snippits==<br />
<br />
The cross-compiler inserts snippets of HF code when it encounters an HSPEAK node of a given type. For example, at node 10, given a "number" node with value '''75''', it inserts:<br />
<br />
\[10] {<br />
75<br />
}<br />
<br />
Henceforth, we shall refer to a node of ID n as: '''\[n] {}''' --this allows us to generalize HSPEAK nodes into simple templates. Here are the snippets used by the cross-compiler; we repeat numbers for the sake of completeness:<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Numbers'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">number</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Do Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | do<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''If Statements'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | if<br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">then_y</span><br><span style="color:green;font-weight:bold">else_z</span><br />
| valign="top" | <br>\[n]{<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[z]()</span><br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Then/Else Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | then/else<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[y]()</span><br>&nbsp;&nbsp;...<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Break/Continue'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">command</span><br />
| valign="top" | <span style="color:green;font-weight:bold">amount</span><br>''if amount==1 then:''<br>&nbsp;&nbsp;''skip amount''<br>''else:''<br>&nbsp;&nbsp;''append "_x" to command''<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[amount]</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">command</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Returning'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">return</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;''invalid''<br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''While/For'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">while</span><br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;not if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">for</span><br />
| valign="top" | <span style="color:green;font-weight:bold">count_id_x</span><br><span style="color:green;font-weight:bold">counter_start_s</span><br><span style="color:green;font-weight:bold">count_end_e</span><br><span style="color:green;font-weight:bold">counter_step_w</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | <span style="color:#0000AA;font-weight:bold">#Template: -2 4 set_var will set local variable 1 to value 4</span><br>\set_var {<br>&nbsp;&nbsp;swap<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;@[]<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;@[.G]<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br><span style="color:#0000AA;font-weight:bold">#Template: 2 get_var will return the contents of global variable 2</span><br>\get_var {<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;[]@<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;[.G]@<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br>\[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[s]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;set_var<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;0 gt<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;gt xor not<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[e]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;neq and<br>&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">[w]()</span><br>&nbsp;&nbsp;&nbsp;&nbsp;add<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| colspan="4" | '''''Note 1:''''' ''The block <span style="color:green">inline_y</span> simply unrolls the <span style="color:green">do_y</span> block into the body of '''[n]()'''. This is done so that '''break''' and '''continue''' will function properly.'' <br>'''''Note 2:''''' ''The upshot of this is that <span style="color:green">do_y</span> will be instantly culled from the source, unless another node references it (which would be a bit of a hack, in my opinion. Regardless, this is fine, and will not affect program validity in any way.''<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Switch'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | switch<br />
| valign="top" | ???<br />
| valign="top" | ''This is not yet documented in [[HSZ]], so we will deal with it later.<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Variable Access'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">global</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[.G]@<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">local</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[x]()</span><br>&nbsp;&nbsp;[]@<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Math Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">set_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">increment_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;add<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">decrement_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;sub<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">not</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;not<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">operand</span><br>''If the operand is listed <br>above, use '''that''' code <br>block, not this one.''<br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[l]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[r]()</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">operand</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Built-In and User-Defined Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">built-in</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;hspeak_api_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">user-script</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;user_script_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|}</div>
Sorlok reaves
https://rpg.hamsterrepublic.com/ohrrpgce/index.php?title=HSP2HF&diff=18560
HSP2HF
2008-12-21T02:26:58Z
<p>Sorlok reaves: /* General syntax clean-up. */</p>
<hr />
<div>{{FMF}}<br />
<br />
Most of you will never have to touch Henceforth if you don't want to. (But who wouldn't want to?) The HSP2HF cross-compiler is used to automatically convert your HamsterSpeak into Henceforth bytecode. It is part of the RPG2XRPG conversion process.<br />
<br />
<br />
==How the Cross-Compiler Works: Motivations==<br />
<br />
Some parts of the OHRRPGCE FMF project are slightly incompatible with the standard OHRRPGCE, but for scripting this is simply unacceptable. Unlike things like, say, a slightly jittery enemy HP value -which is immediately apparent when you first load your game on a phone- a silent error in a script is worth hours of headache-inducing debugging, and probably not worth anything at all in the end. <br />
<br />
So, the goal of the cross-compiler is script-level compatibility. Efficiency and conciseness, while important, take a backseat to this driving need.<br />
<br />
<br />
==How the Cross-Compiler Works: Naive Compiler==<br />
<br />
The Henceforth cross-compiler benefits from HamsterSpeak's tree-like structure; a naive conversion can simply convert each node to a local Henceforth function. Consider the following script (from Wandering Hamster, loaded in the HSP2HF utility)<br />
<br />
[[Image:FMF_cross_compiler_screenshot.png]]<br />
<br />
Typical to most HSZ scripts, "setnpcspeed" contains a head "do" node. This node happens to contain only one element, which takes three arguments, each of which is a simple number or variable. Clicking "cross-compile" will invoke the naive converter, which produces the following code:<br />
<br />
\[14]{<br />
1<br />
L[]@<br />
}<br />
\[12]{<br />
3<br />
}<br />
\[10]{<br />
0<br />
L[]@<br />
}<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
<br />
#Init local variables<br />
@L[1]<br />
@L[0]<br />
<br />
#Main script loop<br />
do_start<br />
[4]()<br />
do_end<br />
<br />
Let's start from the local variables section:<br />
@L[1]<br />
This is a shorthand syntax; it basically calls "local store", a function deep within the script interpreter which does something like this:<br />
void localStore(int arg) {<br />
local_variables[arg] = pop_parent();<br />
}<br />
<br />
Next, we have the main loop. The "do_start" and "do_end" primitives are there to help the "break" and "continue" primitives to function properly. The meat of the main loop is the call to:<br />
[4]()<br />
...which is simply a call to a script-local subroutine.<br />
<br />
The following code defines script-local subroutine 4:<br />
\[4]{<br />
[10]()<br />
[12]()<br />
[14]()<br />
[HS:78]()<br />
}<br />
...as three script-local calls([10], [12], and [14]) and one built-in function call ('''H'''amster'''S'''peak:78, alterNPC). The remaining local functions are equally easy to understand. For example, "[1]@" calls "local load" with "1" as an argument. Local load is defined internally as:<br />
void localLoad(arg1) {<br />
push(local_variables[arg1]);<br />
}<br />
<br />
<br />
==How the Cross-Compiler Works: Reasonable Inlining==<br />
<br />
Simple functions like "setnpcspeed" are very easy to inline, just by copying the leaf nodes' source into their parents. The previous script can be re-written as: <br />
#Init local variables<br />
@[1]<br />
@[0]<br />
<br />
#Main script loop<br />
do_start<br />
[0]@<br />
3<br />
[1]@<br />
[HS:78]()<br />
do_end<br />
...which is much more concise. Due to the nature of HF bytecode, inlining usually improves both performance and storage efficiency. (When I have time to profile, I hope to collect some facts to back this up.) However, inlining everything is often either impossible or unwise, which is why one needs a policy for inlining. At the moment, the OHRRPGCE FMF's cross-compiler uses the following algorithm to determine what to inline:<br />
1) Start by doing a naive conversion, and retain the tree structure. <br />
Mark all nodes as "not dead" and with a "count" of 1.<br />
2) Loop until all nodes are inlined or dead. At each iteration, do the following.<br />
3) Determine which nodes are leaf nodes. (Leaf nodes have ''no'' children, or only dead children).<br />
a) If this node cannot be inlined (e.g., it's self-referential, or checks b/c/d/e below fail), <br />
mark it as "dead".<br />
b) If this node's count is "1", inline it. (Copy its corresponding source into any node that <br />
references it, incrementing their "count" value by 1, and then delete it.)<br />
c) If this node's count is "2", and it is referenced 8 times or less, inline it.<br />
d) If this node's count is "3", and it is referenced 4 times or less, inline it.<br />
e) If this node's count is "4" or more, only inline it if it is referenced exactly once.<br />
<br />
We are still discussing what makes a node impossible to inline. Technically, the problem is difficult, but Hamsterspeak byte-code is fairly structured in nature, which means we can probably define a few simple criteria for exclusion. <br />
<br />
<br />
==Primitives & HSPEAK->HF Snippits==<br />
<br />
The cross-compiler inserts snippets of HF code when it encounters an HSPEAK node of a given type. For example, at node 10, given a "number" node with value '''75''', it inserts:<br />
<br />
\[10] {<br />
75<br />
}<br />
<br />
Henceforth, we shall refer to a node of ID n as: '''\[n] {}''' --this allows us to generalize HSPEAK nodes into simple templates. Here are the snippets used by the cross-compiler; we repeat numbers for the sake of completeness:<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Numbers'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">number</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Do Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | do<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_x</span><br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_y</span><br>&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''If Statements'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | if<br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">then_y</span><br><span style="color:green;font-weight:bold">else_z</span><br />
| valign="top" | <br>\[n]{<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_x</span><br>&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_y</span><br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_z</span><br>&nbsp;&nbsp;}<br>}<br />
|}<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Then/Else Loops'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | then/else<br />
| valign="top" | <span style="color:green;font-weight:bold">node_x</span><br><span style="color:green;font-weight:bold">node_y</span><br>...<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_x</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_y</span><br>&nbsp;&nbsp;...<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Break/Continue'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">command</span><br />
| valign="top" | <span style="color:green;font-weight:bold">amount</span><br>''if amount==1 then:''<br>&nbsp;&nbsp;''skip amount''<br>''else:''<br>&nbsp;&nbsp;''append "_x" to command''<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">[amount]</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">command</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Returning'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">return</span><br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;''invalid''<br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">exitscript</span><br>''At a given <span style="color:green;font-weight:bold">depth</span>''<br />
| valign="top" | <span style="color:green;font-weight:bold">value</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">value</span><br>&nbsp;&nbsp;@[-1]<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">depth</span><br>&nbsp;&nbsp;break_x<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="80%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''While/For'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">while</span><br />
| valign="top" | <span style="color:green;font-weight:bold">conditional_x</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_x</span><br>&nbsp;&nbsp;&nbsp;&nbsp;not if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | <span style="color:green;font-weight:bold">for</span><br />
| valign="top" | <span style="color:green;font-weight:bold">count_id_x</span><br><span style="color:green;font-weight:bold">counter_start_s</span><br><span style="color:green;font-weight:bold">count_end_e</span><br><span style="color:green;font-weight:bold">counter_step_w</span><br><span style="color:green;font-weight:bold">do_y</span><br />
| valign="top" | <span style="color:#0000AA;font-weight:bold">#Template: -2 4 set_var will set local variable 1 to value 4</span><br>\set_var {<br>&nbsp;&nbsp;swap<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;@[]<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;@[.G]<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br><span style="color:#0000AA;font-weight:bold">#Template: 2 get_var will return the contents of global variable 2</span><br>\get_var {<br>&nbsp;&nbsp;dup<br>&nbsp;&nbsp;0 lt if {<br>&nbsp;&nbsp;&nbsp;&nbsp;1 add -1 mult<br>&nbsp;&nbsp;&nbsp;&nbsp;[]@<br>&nbsp;&nbsp;} else {<br>&nbsp;&nbsp;&nbsp;&nbsp;[.G]@<br>&nbsp;&nbsp;}<br>}<br>&nbsp;<br>\[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_x</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_s</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;set_var<br>&nbsp;&nbsp;do {<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_w</span><br>&nbsp;&nbsp;&nbsp;&nbsp;0 gt<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_x</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_e</span><br>&nbsp;&nbsp;&nbsp;&nbsp;gt xor not<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_x</span>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_e</span><br>&nbsp;&nbsp;&nbsp;&nbsp;neq and<br>&nbsp;&nbsp;&nbsp;&nbsp;if {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">inline_y</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;y_command_z<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_x</span><br>&nbsp;&nbsp;&nbsp;&nbsp;get_var<br>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_w</span><br>&nbsp;&nbsp;&nbsp;&nbsp;add<br>&nbsp;&nbsp;}<br>}<br />
|-<br />
| colspan="4" | '''''Note 1:''''' ''The block <span style="color:green">inline_y</span> simply unrolls the <span style="color:green">do_y</span> block into the body of '''loc_n'''. This is done so that '''break''' and '''continue''' will function properly.'' <br>'''''Note 2:''''' ''The upshot of this is that <span style="color:green">do_y</span> will be instantly culled from the source, unless another node references it (which would be a bit of a hack, in my opinion. Regardless, this is fine, and will not affect program validity in any way.''<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Switch'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">flow</span><br />
| valign="top" | switch<br />
| valign="top" | ???<br />
| valign="top" | ''This is not yet documented in [[HSZ]], so we will deal with it later.<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Variable Access'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">global</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_x</span><br>&nbsp;&nbsp;[.G]@<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">local</span><br />
| valign="top" | <span style="color:green;font-weight:bold">variable_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_x</span><br>&nbsp;&nbsp;[]@<br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Math Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">set_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_l</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_r</span><br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">increment_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_l</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_r</span><br>&nbsp;&nbsp;add<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">decrement_variable</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_l</span><br>&nbsp;&nbsp;get_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_r</span><br>&nbsp;&nbsp;sub<br>&nbsp;&nbsp;set_var <span style="color:#0000AA;font-weight:bold"> # As defined above</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">not</span><br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_l</span><br>&nbsp;&nbsp;not<br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">math</span><br />
| valign="top" | <span style="color:green;font-weight:bold">operand</span><br>''If the operand is listed <br>above, use '''that''' code <br>block, not this one.''<br />
| valign="top" | <span style="color:green;font-weight:bold">lhs_l</span><br><span style="color:green;font-weight:bold">rhs_r</span><br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_l</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">loc_r</span><br>&nbsp;&nbsp;<span style="color:green;font-weight:bold">operand</span><br>}<br />
|}<br />
<br />
<br />
<br />
{| border="1" cellpadding="3" rules="all" width="60%"<br />
|bgcolor="#C8FFC8" colspan="4" |'''''Built-In and User-Defined Functions'''''<br />
|-<br />
|bgcolor="#C8FFC8" colspan="3" |''HSpeak Parameters''<br />
|bgcolor="#C8FFC8" rowspan="2" |''Henceforth Snippet''<br />
|-<br />
|bgcolor="#C8FFC8" | ''Kind''<br />
|bgcolor="#C8FFC8" | ''ID''<br />
|bgcolor="#C8FFC8" | ''args[]''<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">built-in</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;hspeak_api_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|-<br />
| valign="top" | <span style="color:blue;font-weight:bold">user-script</span><br />
| valign="top" | <span style="color:green;font-weight:bold">func_id_x</span><br />
| valign="top" | &nbsp;<br />
| valign="top" | \[n] {<br>&nbsp;&nbsp;user_script_<span style="color:green;font-weight:bold">call_x</span><br>}<br />
|}</div>
Sorlok reaves