mirror of https://github.com/OpenMW/openmw.git
Merge commit 'upstream/master'
commit
d8c99c6ce3
@ -0,0 +1,2 @@
|
||||
old
|
||||
run.sh
|
@ -1,58 +0,0 @@
|
||||
#ifndef ENGINE_MYGUI_MANAGER_H
|
||||
#define ENGINE_MYGUI_MANAGER_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <MyGUI.h>
|
||||
#include <MyGUI_OgrePlatform.h>
|
||||
|
||||
namespace GUI
|
||||
{
|
||||
class MyGUIManager
|
||||
{
|
||||
MyGUI::OgrePlatform *mPlatform;
|
||||
MyGUI::Gui *mGui;
|
||||
|
||||
public:
|
||||
MyGUIManager() : mPlatform(NULL), mGui(NULL) {}
|
||||
MyGUIManager(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false)
|
||||
{ setup(wnd,mgr,logging); }
|
||||
~MyGUIManager() { shutdown(); }
|
||||
|
||||
void setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false)
|
||||
{
|
||||
assert(wnd);
|
||||
assert(mgr);
|
||||
|
||||
using namespace MyGUI;
|
||||
|
||||
// Enable/disable MyGUI logging to stdout. (Logging to MyGUI.log
|
||||
// is still enabled.) In order to do this we have to initialize
|
||||
// the log manager before the main gui system itself, otherwise
|
||||
// the main object will get the chance to spit out a few messages
|
||||
// before we can able to disable it.
|
||||
LogManager::initialise();
|
||||
LogManager::setSTDOutputEnabled(logging);
|
||||
|
||||
// Set up OGRE platform. We might make this more generic later.
|
||||
mPlatform = new OgrePlatform();
|
||||
mPlatform->initialise(wnd, mgr);
|
||||
|
||||
// Create GUI
|
||||
mGui = new Gui();
|
||||
mGui->initialise();
|
||||
}
|
||||
|
||||
void shutdown()
|
||||
{
|
||||
if(mGui) delete mGui;
|
||||
if(mPlatform)
|
||||
{
|
||||
mPlatform->shutdown();
|
||||
delete mPlatform;
|
||||
}
|
||||
mGui = NULL;
|
||||
mPlatform = NULL;
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
@ -0,0 +1,103 @@
|
||||
project(OpenMW)
|
||||
|
||||
# local files
|
||||
|
||||
set(GAME
|
||||
main.cpp
|
||||
engine.cpp)
|
||||
set(GAME_HEADER
|
||||
engine.hpp)
|
||||
source_group(game FILES ${GAME} ${GAME_HEADER})
|
||||
|
||||
set(GAMEREND
|
||||
mwrender/mwscene.cpp
|
||||
mwrender/cellimp.cpp
|
||||
mwrender/interior.cpp
|
||||
mwrender/sky.cpp)
|
||||
set(GAMEREND_HEADER
|
||||
mwrender/cell.hpp
|
||||
mwrender/cellimp.hpp
|
||||
mwrender/mwscene.hpp
|
||||
mwrender/interior.hpp
|
||||
mwrender/playerpos.hpp
|
||||
mwrender/sky.hpp)
|
||||
source_group(apps\\openmw\\mwrender FILES ${GAMEREND} ${GAMEREND_HEADER})
|
||||
|
||||
# set(GAMEINPUT)
|
||||
set(GAMEINPUT_HEADER
|
||||
mwinput/inputmanager.hpp)
|
||||
source_group(apps\\openmw\\mwinput FILES ${GAMEINPUT} ${GAMEINPUT_HEADER})
|
||||
|
||||
set(GAMESCRIPT
|
||||
mwscript/scriptmanager.cpp
|
||||
mwscript/compilercontext.cpp
|
||||
mwscript/interpretercontext.cpp
|
||||
mwscript/cellextensions.cpp
|
||||
mwscript/miscextensions.cpp
|
||||
mwscript/guiextensions.cpp
|
||||
mwscript/extensions.cpp
|
||||
mwscript/globalscripts.cpp
|
||||
)
|
||||
set(GAMESCRIPT_HEADER
|
||||
mwscript/locals.hpp
|
||||
mwscript/scriptmanager.hpp
|
||||
mwscript/compilercontext.hpp
|
||||
mwscript/interpretercontext.hpp
|
||||
mwscript/cellextensions.hpp
|
||||
mwscript/miscextensions.hpp
|
||||
mwscript/guiextensions.hpp
|
||||
mwscript/extensions.hpp
|
||||
mwscript/globalscripts.hpp
|
||||
)
|
||||
source_group(apps\\openmw\\mwscript FILES ${GAMESCRIPT} ${GAMESCRIPT_HEADER})
|
||||
|
||||
set(GAMESOUND
|
||||
mwsound/soundmanager.cpp
|
||||
mwsound/extensions.cpp)
|
||||
set(GAMESOUND_HEADER
|
||||
mwsound/soundmanager.hpp
|
||||
mwsound/extensions.hpp)
|
||||
source_group(apps\\openmw\\mwsound FILES ${GAMESOUND} ${GAMESOUND_HEADER})
|
||||
|
||||
set(GAMEGUI
|
||||
mwgui/guimanager.cpp)
|
||||
set(GAMEGUI_HEADER
|
||||
mwgui/guimanager.hpp)
|
||||
source_group(apps\\openmw\\mwgui FILES ${GAMEGUI} ${GAMEGUI_HEADER})
|
||||
|
||||
set(GAMEWORLD
|
||||
mwworld/world.cpp)
|
||||
set(GAMEWORLD_HEADER
|
||||
mwworld/refdata.hpp
|
||||
mwworld/world.hpp
|
||||
mwworld/ptr.hpp
|
||||
mwworld/environment.hpp
|
||||
)
|
||||
source_group(apps\\openmw\\mwworld FILES ${GAMEWORLD} ${GAMEWORLD_HEADER})
|
||||
|
||||
|
||||
set(OPENMW_CPP ${GAME} ${GAMEREND} ${GAMEINPUT} ${GAMESCRIPT} ${GAMESOUND} ${GAMEGUI} ${GAMEWORLD})
|
||||
set(OPENMW_HEADER ${GAME_HEADER} ${GAMEREND_HEADER} ${GAMEINPUT_HEADER} ${GAMESCRIPT_HEADER}
|
||||
${GAMESOUND_HEADER} ${GAMEGUI_HEADER} ${GAMEWORLD_HEADER})
|
||||
|
||||
# Main executable
|
||||
add_executable(openmw
|
||||
${COMPONENTS} ${COMPONENTS_HEADER}
|
||||
${OPENMW_LIBS} ${OPENMW_LIBS_HEADER}
|
||||
${OPENMW_CPP} ${OPENMW_HEADER}
|
||||
${APPLE_BUNDLE_RESOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(openmw
|
||||
${OGRE_LIBRARIES}
|
||||
${OIS_LIBRARIES}
|
||||
${Boost_LIBRARIES}
|
||||
caelum
|
||||
MyGUIEngine
|
||||
MyGUI.OgrePlatform
|
||||
)
|
||||
|
||||
if (APPLE)
|
||||
find_library(CARBON_FRAMEWORK Carbon)
|
||||
target_link_libraries(openmw ${CARBON_FRAMEWORK})
|
||||
endif (APPLE)
|
@ -0,0 +1,43 @@
|
||||
#include <MyGUI.h>
|
||||
#include <MyGUI_OgrePlatform.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "manager.hpp"
|
||||
|
||||
using namespace GUI;
|
||||
|
||||
void MyGUIManager::setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging)
|
||||
{
|
||||
assert(wnd);
|
||||
assert(mgr);
|
||||
|
||||
using namespace MyGUI;
|
||||
|
||||
// Enable/disable MyGUI logging to stdout. (Logging to MyGUI.log is
|
||||
// still enabled.) In order to do this we have to initialize the log
|
||||
// manager before the main gui system itself, otherwise the main
|
||||
// object will get the chance to spit out a few messages before we
|
||||
// can able to disable it.
|
||||
LogManager::initialise();
|
||||
LogManager::setSTDOutputEnabled(logging);
|
||||
|
||||
// Set up OGRE platform. We might make this more generic later.
|
||||
mPlatform = new OgrePlatform();
|
||||
mPlatform->initialise(wnd, mgr);
|
||||
|
||||
// Create GUI
|
||||
mGui = new Gui();
|
||||
mGui->initialise();
|
||||
}
|
||||
|
||||
void MyGUIManager::shutdown()
|
||||
{
|
||||
if(mGui) delete mGui;
|
||||
if(mPlatform)
|
||||
{
|
||||
mPlatform->shutdown();
|
||||
delete mPlatform;
|
||||
}
|
||||
mGui = NULL;
|
||||
mPlatform = NULL;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
#ifndef ENGINE_MYGUI_MANAGER_H
|
||||
#define ENGINE_MYGUI_MANAGER_H
|
||||
|
||||
namespace MyGUI
|
||||
{
|
||||
class OgrePlatform;
|
||||
class Gui;
|
||||
}
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class RenderWindow;
|
||||
class SceneManager;
|
||||
}
|
||||
|
||||
namespace GUI
|
||||
{
|
||||
class MyGUIManager
|
||||
{
|
||||
MyGUI::OgrePlatform *mPlatform;
|
||||
MyGUI::Gui *mGui;
|
||||
|
||||
public:
|
||||
MyGUIManager() : mPlatform(NULL), mGui(NULL) {}
|
||||
MyGUIManager(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false)
|
||||
{ setup(wnd,mgr,logging); }
|
||||
~MyGUIManager() { shutdown(); }
|
||||
|
||||
void setup(Ogre::RenderWindow *wnd, Ogre::SceneManager *mgr, bool logging=false);
|
||||
void shutdown();
|
||||
};
|
||||
}
|
||||
#endif
|
@ -1,7 +1,20 @@
|
||||
#ifndef MWGUI_LAYOUTS_H
|
||||
#define MWGUI_LAYOUTS_H
|
||||
|
||||
#include "layout.hpp"
|
||||
#include <components/engine/gui/layout.hpp>
|
||||
|
||||
/*
|
||||
This file contains classes corresponding to all the window layouts
|
||||
defined in resources/mygui/ *.xml.
|
||||
|
||||
Each class inherites GUI::Layout and loads the XML file, and
|
||||
provides some helper functions to manipulate the elements of the
|
||||
window.
|
||||
|
||||
The windows are never created or destroyed (except at startup and
|
||||
shutdown), they are only hid. You can control visibility with
|
||||
setVisible().
|
||||
*/
|
||||
|
||||
namespace MWGUI
|
||||
{
|
@ -1,247 +0,0 @@
|
||||
OpenMW - the completely unofficial reimplementation of Morrowind
|
||||
================================================================
|
||||
|
||||
OpenMW is an open source reimplementation of the Morrowind game
|
||||
engine. For more information, see README.txt or
|
||||
|
||||
http://openmw.snaptoad.com/
|
||||
|
||||
|
||||
|
||||
|
||||
Installation from source
|
||||
========================
|
||||
|
||||
|
||||
Supported Unix platforms:
|
||||
-------------------------
|
||||
|
||||
OpenMW has been tested for the most part on 32bit Ubuntu Linux
|
||||
8.04. It should, however, also work on most other 32bit
|
||||
distributions.
|
||||
|
||||
FreeBSD is also known to work with recent versions of GDC (the D
|
||||
compiler), but you might need to rebuild some of the dependencies from
|
||||
source yourself. (If you have done this and can give more specific
|
||||
instructions, please let me know!)
|
||||
|
||||
64bit systems are NOT supported.
|
||||
|
||||
If you manage to build OpenMW on a platform not listed here, or want
|
||||
to try, please let me know!
|
||||
|
||||
|
||||
|
||||
Dependencies:
|
||||
-------------
|
||||
|
||||
Dependencies needed to build OpenMW:
|
||||
|
||||
OGRE 1.6.2 (3d engine)
|
||||
OIS-1.0.0 (input system)
|
||||
MyGUI 2.2.2 (GUI system for OGRE)
|
||||
Bullet 2.74 (collision and physics library)
|
||||
Monster 0.12 (scripting engine, included)
|
||||
OpenAL (3d sound system)
|
||||
libavcodec,
|
||||
libavformat (For MP3 playback)
|
||||
gcc and g++ (C++ compiler)
|
||||
GNU make (build tool for C++ files)
|
||||
DMD 1.036 (D compiler)
|
||||
or GDC 4.1.3 (alternative D compiler)
|
||||
|
||||
The above versions are the ones I have tested recently, but other
|
||||
versions might work. OGRE, OpenAL and the other libraries have
|
||||
dependencies of their own, so I recommend using an automated package
|
||||
tool to install as many of these as possible. On Ubuntu, for example,
|
||||
try typing:
|
||||
|
||||
sudo apt-get install libogre-dev libavcodec-dev libavformat-dev libois-dev build-essential g++ gdc
|
||||
|
||||
On Arch Linux, get the following packages from AUR:
|
||||
ogre
|
||||
mygui-svn
|
||||
bullet-2.75
|
||||
openal
|
||||
ffmpeg
|
||||
ois
|
||||
dmd
|
||||
libphobos
|
||||
dsss
|
||||
|
||||
(On Arch you also need to change the Bullet libraries to
|
||||
-llBulletDynamics -llBulletCollision -llLinearMath in dsss.conf, or
|
||||
similarily in the Makefile if you prefer building with make.)
|
||||
|
||||
A note about OpenAL: The library found in most Linux distributions
|
||||
(0.0.8 SI) is outdated. Some distributions are now changing to OpenAL
|
||||
Soft, which has a less hardware-dependent implementation. If you
|
||||
experience sound problems (like stuttering music), try switching to
|
||||
OpenAL Soft.
|
||||
|
||||
Note to UBUNTU 8.10 users: There's a problem with Pulseaudio in this
|
||||
Ubuntu version. If you find that sound doesn't work or you get error
|
||||
messages related to sound, try this page:
|
||||
http://idyllictux.wordpress.com/2008/10/29/alsa-instead-of-pulseaudio-for-ubuntu-810-intrepid-a-non-destructive-way/
|
||||
|
||||
libavcodec and libavformat are part of the FFmpeg package, which is
|
||||
part of the 'mplayer' project. Note that some of the codecs might be
|
||||
patent protected in your country or area.
|
||||
|
||||
If you want to install Ogre, Bullet, OpenAL, OIS or FFmpeg manually,
|
||||
try:
|
||||
|
||||
OGRE: http://ogre3d.org/
|
||||
MyGUI: http://www.ogre3d.org/wiki/index.php/MyGUI
|
||||
OpenAL: http://openal.org/
|
||||
Linux source: http://kcat.strangesoft.net/openal.html
|
||||
OIS: http://sourceforge.net/projects/wgois/
|
||||
FFmpeg: http://ffmpeg.mplayerhq.hu/download.html
|
||||
Bullet: http://bulletphysics.com/
|
||||
Monster: Included with OpenMW.
|
||||
|
||||
|
||||
Setting up Bullet
|
||||
-----------------
|
||||
|
||||
Bullet is not yet included in most Linux distributions, and must
|
||||
usually be compiled manually. Consult the documentation included with
|
||||
Bullet for instructions on compiling.
|
||||
|
||||
After compiling, you should have the files libbulletdynamics.a,
|
||||
libbulletcollision.a and libbulletmath.a. (Depending on build method,
|
||||
you may end up with names like libLibBulletDynamics.a instead.) Copy
|
||||
or link these to bullet/ in the openmw directory (and rename them to
|
||||
the libbulletdynamics.a etc if necessary.)
|
||||
|
||||
Example:
|
||||
cd openmw/bullet
|
||||
ln -s ~/software/bullet-2.74/src/BulletCollision/libbulletcollision.a .
|
||||
|
||||
Next, create a link to the "src" directory in openmw/include/bullet/ .
|
||||
|
||||
Example:
|
||||
cd openmw
|
||||
mkdir -p include
|
||||
cd include
|
||||
ln -s ~/software/bullet-2.74/src bullet
|
||||
|
||||
|
||||
Setting up MyGUI
|
||||
----------------
|
||||
|
||||
Like Bullet, MyGUI is still too fresh to be prepackaged with the major
|
||||
Linux distros. To make matters worse, there's not even a recent source
|
||||
package. You'll need to get the source from svn (at
|
||||
https://my-gui.svn.sourceforge.net/svnroot/my-gui/trunk ). Revisions
|
||||
at around 1738 should work, but newer revisions are probably better.
|
||||
|
||||
Once you've compiled the sources, install normally using 'make
|
||||
install' (check the MyGUI docs for any instructions), and the OpenMW
|
||||
makefile should pick up its location automatically.
|
||||
|
||||
|
||||
Choosing a D compiler
|
||||
---------------------
|
||||
|
||||
The above apt-get command installs the GDC compiler. There are
|
||||
currently two choices for the D compiler, DMD and GDC. DMD is the
|
||||
"official" compiler and is updated more often, while GDC is a
|
||||
completely open source front-end for GCC (the GNU compiler.) Both
|
||||
should work equally well with OpenMW.
|
||||
|
||||
If you want to install GDC manually, go to
|
||||
http://sourceforge.net/projects/dgcc
|
||||
|
||||
If you want to use DMD instead, it can be found at:
|
||||
http://digitalmars.com/d/1.0/dmd-linux.html
|
||||
|
||||
Note that the DMD unstable branch (2.000 and up) will NOT currently
|
||||
work with OpenMW. Use the stable branch (1.x) instead.
|
||||
|
||||
Also note that DMD is only available on 32 bit x86 Linux - other
|
||||
operating systems or architectures will have to use GDC. Note however
|
||||
that GDC might be unstable on these platforms as well, especially on
|
||||
64 bit platforms.
|
||||
|
||||
|
||||
|
||||
Building:
|
||||
---------
|
||||
|
||||
After installing all the dependencies, you can try running "make cpp"
|
||||
first to see if the C++ parts compile. You may need to alter the
|
||||
Makefile if you are using non-standard include paths, etc. If you have
|
||||
DSSS installed (a D build tool), type:
|
||||
|
||||
dsss build
|
||||
|
||||
If you do NOT have DSSS, try using make
|
||||
|
||||
make all
|
||||
|
||||
You might need to edit the Makefile to match your setup. If you are
|
||||
using DMD instead of GDC, try changing the compiler from "gdmd" to
|
||||
"dmd" in the Makefile.
|
||||
|
||||
If all else fails, you can try the build script:
|
||||
|
||||
./build_openmw.sh
|
||||
|
||||
This build method is deprecated and only works with gdc.
|
||||
|
||||
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Before you can run OpenMW, you have to help it find the Morrowind data
|
||||
files. The 'openmw' program needs the files Morrowind.esm and
|
||||
Morrowind.bsa, and the directories Sound/ and Music/ from your
|
||||
"Morrowind\Data Files\" directory. By default it expects to find these
|
||||
in the data/ directory. (This can be changed in openmw.ini)
|
||||
|
||||
I recommend creating a symbolic link to your original Morrowind
|
||||
install. For example, if you have Morrowind installed in:
|
||||
|
||||
c:\Program Files\Bethesda Softworks\Morrowind\
|
||||
|
||||
and your windows c: drive is mounted on /media/hda1, then run the
|
||||
following command:
|
||||
|
||||
ln -s "/media/hda1/Program Files/Bethesda Softworks/Morrowind/Data Files/" data
|
||||
|
||||
Also, if you have OGRE installed in a non-standard directory (ie. NOT
|
||||
in /usr/lib/OGRE), you have to change the PluginFolder in the file
|
||||
plugins.cfg.linux.
|
||||
|
||||
The first time you run openmw you will be asked to set screen
|
||||
resolution and other graphics settings. You can bring this dialogue up
|
||||
at any time with the -oc command line switch. I don't recommend using
|
||||
fullscreen mode yet, since it might mess up your screen and input
|
||||
settings if the program crashes.
|
||||
|
||||
|
||||
|
||||
|
||||
Running OpenMW
|
||||
==============
|
||||
|
||||
If Azura is with you and all the stars and planets are aligned in your
|
||||
favor, you should now be able to run OpenMW using the program called
|
||||
'openmw'.
|
||||
|
||||
Write openmw -h to see a list of options.
|
||||
|
||||
Running without parameters should bring you into the cave called Assu,
|
||||
or the last cell loaded. Move around with WASD (or arrow keys), change
|
||||
physics mode (walking, flying, ghost) with 't', exit with 'q' or
|
||||
escape.
|
||||
|
||||
To load another cell, specify the cell name on the command line. Use
|
||||
the 'esmtool' program to get a list of cells. Note that you must use
|
||||
quotation marks "" if the cell name contains spaces or other weird
|
||||
characters.
|
||||
|
||||
Enjoy! ;-)
|
@ -1,83 +0,0 @@
|
||||
OpenMW - the completely unofficial reimplementation of Morrowind
|
||||
================================================================
|
||||
|
||||
OpenMW is an open source reimplementation of the Morrowind game
|
||||
engine. For more information, see README.txt or
|
||||
|
||||
http://openmw.snaptoad.com/
|
||||
|
||||
|
||||
|
||||
|
||||
Building from source
|
||||
====================
|
||||
|
||||
|
||||
Supported Windows platforms:
|
||||
----------------------------
|
||||
|
||||
Mostly tested on Windows XP, but Vista is also rumored to work. If you
|
||||
manage to compile or run OpenMW on another Windows platform
|
||||
(9x/Me/NT/2000), please let me know.
|
||||
|
||||
At the moment, only command line building is supported.
|
||||
|
||||
|
||||
|
||||
Dependencies:
|
||||
-------------
|
||||
|
||||
To compile OpenMW you need Mingw with g++ (a C++ compiler) and gdc
|
||||
(the D compiler.) You can find them here:
|
||||
|
||||
Mingw: http://sourceforge.net/projects/mingw/
|
||||
gdc: http://sourceforge.net/projects/gdcwin/
|
||||
|
||||
Note that the "official" D compiler, DMD, will not currently work on
|
||||
Windows, because it is uses an object format incompatible with most
|
||||
C++ compilers.
|
||||
|
||||
All library dependencies are included in the file
|
||||
openmw-dll-pack.zip. Simply download this file and unpack it in the
|
||||
same directory as the OpenMW source code. You should not need to
|
||||
download any other dependencies.
|
||||
|
||||
|
||||
|
||||
Setting everything up
|
||||
---------------------
|
||||
|
||||
First, install Mingw (get the automatic installer.) Make sure gcc and
|
||||
g++ packages are selected.
|
||||
|
||||
Next install the gdcwin installer (the package named 'gdc') and
|
||||
install it in the same directory as Mingw.
|
||||
|
||||
Open a command line. Set up your PATH to include Mingw and gdc
|
||||
(eg. "set PATH=%PATH%;c:\mingw\bin").
|
||||
|
||||
Next you must make sure the D include files are found by the
|
||||
compiler. Run the following command:
|
||||
set DFLAGS=-Ic:\mingw\include\d\3.4.5\
|
||||
where the path following -I is the path of your Mingw GDC include
|
||||
directory.
|
||||
|
||||
Make sure the commands g++ and gdc work (should output "no input
|
||||
files".)
|
||||
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
To build, simply run build_openmw.bat
|
||||
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
For instructions on how to set everything up after compilation, see
|
||||
README-win32.txt.
|
||||
|
||||
Good luck!
|
@ -1,115 +0,0 @@
|
||||
# Designed for GNU Make
|
||||
|
||||
# Compiler settings
|
||||
CXXFLAGS?= -g
|
||||
DMD=gdmd -version=Posix
|
||||
|
||||
# Some extra flags for niftool and bsatool
|
||||
NIFFLAGS=-debug=warnstd -debug=check -debug=statecheck -debug=strict -debug=verbose
|
||||
|
||||
# Linker flags
|
||||
LFLAGS= -L-lopenal -L-lOgreMain -L-lOIS -L-lmygui -L-luuid -L-lavcodec -L-lavformat bullet/libbulletdynamics.a bullet/libbulletcollision.a bullet/libbulletmath.a
|
||||
|
||||
# Compiler settings for Ogre, OIS and MyGUI
|
||||
# TODO: the -I when we're done
|
||||
CF_OIS=$(shell pkg-config --cflags OIS OGRE MyGUI) -Iterrain/
|
||||
OGCC=$(CXX) $(CXXFLAGS) $(CF_OIS)
|
||||
|
||||
# Compiler settings for ffmpeg.
|
||||
CF_FFMPEG=$(shell pkg-config --cflags libavcodec libavformat)
|
||||
AVGCC=$(CXX) $(CXXFLAGS) $(CF_FFMPEG)
|
||||
|
||||
# Settings for Bullet
|
||||
CF_BULLET=-Iinclude/bullet
|
||||
BGCC=$(CXX) $(CXXFLAGS) $(CF_BULLET)
|
||||
|
||||
# Ogre C++ files, on the form ogre/cpp_X.cpp. Only the first file is
|
||||
# passed to the compiler, the rest are dependencies.
|
||||
ogre_cpp=ogre framelistener interface bsaarchive
|
||||
|
||||
# MyGUI C++ files, gui/cpp_X.cpp. These are currently included
|
||||
# cpp_ogre.o with cpp_ogre.cpp.
|
||||
mygui_cpp=mygui console
|
||||
|
||||
# Ditto for the landscape engine, in terrain/cpp_X.cpp
|
||||
terrain_cpp=baseland terrain mesh
|
||||
|
||||
# FFmpeg files, in the form sound/cpp_X.cpp.
|
||||
avcodec_cpp=avcodec
|
||||
|
||||
# Bullet cpp files
|
||||
bullet_cpp=bullet player scale
|
||||
|
||||
#### No modifications should be required below this line. ####
|
||||
|
||||
ogre_cpp_files=\
|
||||
$(ogre_cpp:%=ogre/cpp_%.cpp) \
|
||||
$(mygui_cpp:%=gui/cpp_%.cpp) \
|
||||
$(terrain_cpp:%=terrain/cpp_%.cpp)
|
||||
avcodec_cpp_files=$(avcodec_cpp:%=sound/cpp_%.cpp)
|
||||
bullet_cpp_files=$(bullet_cpp:%=bullet/cpp_%.cpp)
|
||||
|
||||
# All object files needed by openmw and esmtool
|
||||
src := $(wildcard bsa/*.d) $(wildcard bullet/*.d) $(wildcard core/*.d) \
|
||||
$(wildcard esm/*.d) $(wildcard input/*.d) $(wildcard nif/*.d) $(wildcard ogre/*.d) \
|
||||
$(wildcard scene/*.d) $(wildcard sound/*.d) $(wildcard util/*.d) $(wildcard gui/*.d)
|
||||
src := $(src) $(wildcard mscripts/*.d) $(wildcard terrain/*.d)
|
||||
src := $(src) monster/monster.d \
|
||||
$(wildcard monster/vm/*.d) \
|
||||
$(wildcard monster/compiler/*.d) \
|
||||
$(wildcard monster/util/*.d) \
|
||||
$(wildcard monster/modules/*.d)
|
||||
obj := $(src:%.d=objs/%.o)
|
||||
|
||||
# The NIF object files for niftool and bsatool are put in a separate
|
||||
# directory, since they are built with different flags.
|
||||
src_nif := $(wildcard nif/*.d)
|
||||
src_nif := $(src_nif) $(wildcard util/*.d)
|
||||
src_nif := $(src_nif) core/memory.d
|
||||
src_nif := $(src_nif) $(wildcard monster/util/*.d)
|
||||
obj_nif := $(src_nif:%.d=nifobjs/%.o)
|
||||
|
||||
.PHONY: cpp all clean
|
||||
|
||||
# Build everything. Default when running 'make' directly.
|
||||
all: openmw esmtool niftool bsatool bored
|
||||
|
||||
# Only build C++ sources. Used when building from DSSS.
|
||||
cpp: cpp_ogre.o cpp_avcodec.o cpp_bullet.o
|
||||
|
||||
cpp_ogre.o: $(ogre_cpp_files)
|
||||
$(OGCC) -o $@ -c $<
|
||||
|
||||
cpp_avcodec.o: $(avcodec_cpp_files)
|
||||
$(AVGCC) -o $@ -c $<
|
||||
|
||||
cpp_bullet.o: $(bullet_cpp_files)
|
||||
$(BGCC) -o $@ -c $<
|
||||
|
||||
objs/%.o: %.d
|
||||
dirname $@ | xargs mkdir -p
|
||||
$(DMD) -c $< -of$@
|
||||
|
||||
nifobjs/%.o: %.d
|
||||
dirname $@ | xargs mkdir -p
|
||||
$(DMD) $(NIFFLAGS) -c $< -of$@
|
||||
|
||||
openmw: openmw.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o $(obj)
|
||||
$(DMD) $^ -of$@ $(LFLAGS)
|
||||
|
||||
esmtool: esmtool.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o $(obj)
|
||||
$(DMD) $^ -of$@ $(LFLAGS)
|
||||
|
||||
niftool: niftool.d $(obj_nif)
|
||||
$(DMD) $^ -of$@
|
||||
|
||||
bsatool: bsatool.d $(obj_nif) objs/bsa/bsafile.o
|
||||
$(DMD) $^ -of$@
|
||||
|
||||
bored: bored.d
|
||||
$(DMD) $^
|
||||
|
||||
clean:
|
||||
-rm -f cpp_bullet.o cpp_ogre.o cpp_avcodec.o bored.o bsafile.o bsatool.o esmtool.o niftool.o openmw.o
|
||||
-rm -f openmw esmtool niftool bsatool bored
|
||||
-rm -rf objs/ nifobjs/ dsss_objs/
|
@ -1,54 +0,0 @@
|
||||
OpenMW - the completely unofficial reimplementation of Morrowind
|
||||
================================================================
|
||||
|
||||
OpenMW is an open source reimplementation of the Morrowind game
|
||||
engine. For more information, see README.txt or
|
||||
|
||||
http://openmw.snaptoad.com/
|
||||
|
||||
|
||||
|
||||
|
||||
Running OpenMW
|
||||
==============
|
||||
|
||||
OpenMW consists of three separate downloads:
|
||||
|
||||
openmw-0.X_win32.zip - binary package (with EXE file)
|
||||
openmw-0.X.zip - source code
|
||||
openmw-dll-pack.zip - library pack
|
||||
|
||||
|
||||
You only need the binary or the source package, not both. The DLL pack
|
||||
is needed in both cases.
|
||||
|
||||
NOTE: If downloaded the SOURCE release, please read COMPILE-win32.txt
|
||||
before reading the rest of this file.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------------
|
||||
|
||||
OpenMW assumes you have the Morrowind data files in c:\Program
|
||||
Files\Bethesda Softworks\Morrowind\Data Files\ . If this is not where
|
||||
you have installed Morrowind, you should edit the file openmw.ini.
|
||||
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
Just run openmw.exe and enjoy! ;-)
|
||||
|
||||
The first time you run OpenMW, you will be asked to set screen
|
||||
resolution and other graphics settings. To be safe, I don't recommend
|
||||
setting fullscreen mode on the first run. You can bring up the
|
||||
dialogue at any time by using the -oc switch.
|
||||
|
||||
Move around with WASD or arrow keys, change physics mode (walking,
|
||||
flying, ghost) with 't', exit with 'q' or escape. You can change key
|
||||
bindings in the openmw.ini file.
|
||||
|
||||
You start in a cell called "Assu". You can change this in openmw.ini or
|
||||
by specifying a cell name on the command line.
|
||||
|
||||
Write openmw -h on the command line to see a complete list of options.
|
@ -1,111 +0,0 @@
|
||||
OpenMW - the completely unofficial reimplementation of Morrowind
|
||||
================================================================
|
||||
|
||||
Written by Nicolay Korslund
|
||||
Email: korslund@gmail.com
|
||||
WWW: http://openmw.sourceforge.net
|
||||
License: See GPL3.txt
|
||||
Current version: 0.6
|
||||
Date: 2009 mar. 2
|
||||
|
||||
|
||||
QUICK NOTE: You must own and install Morrowind before you can use
|
||||
OpenMW. Let me repeat that: OpenMW will NOT run if you do not have
|
||||
Morrowind installed on your system!
|
||||
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Currently supported platforms are Windows, Linux and FreeBSD. Most
|
||||
testing is done on Ubuntu 8.04 and Windows XP Professional.
|
||||
|
||||
For instructions, see one of the following:
|
||||
|
||||
README-win32.txt - instructions for binary Windows release
|
||||
COMPILE-win32.txt - instructions for building from source on Windows
|
||||
COMPILE-linux.tx - instructions for building from source on Linux / Unix
|
||||
|
||||
Linux 64 does NOT work, because of problems with the D compiler. We
|
||||
hope to sort this out at some point, but right now it's simply not
|
||||
supported.
|
||||
|
||||
|
||||
|
||||
Programs included in this package:
|
||||
==================================
|
||||
|
||||
openmw - The main program. Run openmw -h for a list of options.
|
||||
|
||||
esmtool - Used to inspect ES files (ESM, ESP, ESS). Run without
|
||||
arguments to get a list of options.
|
||||
|
||||
bsatool - Tool for viewing and extracting files from BSA archives.
|
||||
(Can also be used to test the NIF parser on a BSA.)
|
||||
|
||||
niftool - Decodes one or more NIF files and prints the details.
|
||||
|
||||
|
||||
|
||||
Changelog:
|
||||
==========
|
||||
|
||||
0.6 (2009 mar. 2) - latest release
|
||||
|
||||
- coded a GUI system using MyGUI
|
||||
- skinned MyGUI to look like Morrowind (work in progress)
|
||||
- integrated the Monster-script engine
|
||||
- rewrote some parts into script code
|
||||
- very early MyGUI <-> Monster binding
|
||||
- fixed Windows sound problems (replaced old openal32.dll)
|
||||
|
||||
|
||||
0.5 (2008 nov. 5)
|
||||
|
||||
- Collision detection with Bullet
|
||||
- Experimental walk & fall character physics
|
||||
- New key bindings:
|
||||
t - toggle physics mode (walking, flying, ghost)
|
||||
n - night-eye: brightens the scene
|
||||
- Fixed incompatability with DMD 1.032 and newer
|
||||
- Various minor changes and updates
|
||||
|
||||
|
||||
0.4 (2008 aug. 30)
|
||||
|
||||
- switched from Audiere to OpenAL (BIG thanks to Chris Robinson)
|
||||
- added complete Makefile (again) as a alternative build tool
|
||||
- more realistic lighting (thanks again to Chris Robinson)
|
||||
- various localization fixes - tested with Russian and French versions
|
||||
- temporary workaround for the Unicode issue: invalid UTF displayed as '?'
|
||||
- added -ns option to disable sound, for debugging
|
||||
- various bug-fixes
|
||||
- cosmetic changes to placate gdc -Wall
|
||||
|
||||
|
||||
0.3 (2008 jul. 10)
|
||||
|
||||
- built and tested on Windows XP
|
||||
- partial support for FreeBSD (exceptions do not work)
|
||||
- temporarily dropped DSSS and Monster as necessary dependencies
|
||||
- renamed main program from 'morro' to 'openmw'
|
||||
- made the config system more robust
|
||||
- added -oc switch for showing Ogre config window on startup
|
||||
- removed some config files, these are auto-generated when
|
||||
missing. Separated plugins.cfg into linux and windows versions.
|
||||
- updated Makefile and sources for increased portability (thanks to
|
||||
Dmitry Marakasov.)
|
||||
- tested against OIS 1.0.0 (Ubuntu repository package)
|
||||
|
||||
|
||||
0.2 (2008 jun. 17)
|
||||
|
||||
- compiles with gdc
|
||||
- switched to DSSS for building D code
|
||||
- includes the program esmtool
|
||||
|
||||
|
||||
0.1 (2008 jun. 03)
|
||||
|
||||
- first release
|
@ -1,11 +0,0 @@
|
||||
@echo off
|
||||
|
||||
rem See COMPILE-win32.txt for instructions.
|
||||
|
||||
echo Compiling C++ files
|
||||
g++ -c sound\cpp_avcodec.cpp -I.\includes\ffmpeg\
|
||||
g++ -c ogre\cpp_ogre.cpp -I.\includes\ogre\ -I.\includes\mygui\
|
||||
g++ -c bullet\cpp_bullet.cpp -I.\includes\bullet\
|
||||
|
||||
echo Compiling main program (openmw.exe)
|
||||
gdc -g openmw.d bsa\*.d core\*.d esm\*.d input\*.d nif\*.d ogre\*.d scene\*.d sound\*.d util\*.d bullet\*.d cpp_ogre.o cpp_avcodec.o cpp_bullet.o libbulletdynamics.a libbulletcollision.a libbulletmath.a mscripts\setup.d monster\monster.d monster\compiler\*.d monster\vm\*.d monster\util\*.d monster\modules\*.d avcodec-51.dll avformat-52.dll avdevice-52.dll avutil-49.dll openal32.dll mygui.a freetype6.dll -lole32 ogremain_d.dll OIS_d.dll -lstdc++ -o openmw.exe
|
@ -1,275 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (inifile.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module core.inifile;
|
||||
|
||||
import std.stdio;
|
||||
import std.stream;
|
||||
import std.string;
|
||||
import std.path;
|
||||
import std.file;
|
||||
|
||||
import monster.util.string;
|
||||
import monster.util.aa;
|
||||
|
||||
void safeMkdir(char[] npath)
|
||||
{
|
||||
char curDir[];
|
||||
int index = 0;
|
||||
int srch;
|
||||
|
||||
do
|
||||
{
|
||||
while(npath[index..$].begins("/"))
|
||||
index++;
|
||||
|
||||
srch = npath[index..$].find('/');
|
||||
|
||||
if(srch == -1)
|
||||
curDir = npath;
|
||||
else
|
||||
{
|
||||
index += srch;
|
||||
curDir = npath[0..index];
|
||||
}
|
||||
index++;
|
||||
|
||||
if(!exists(curDir) || !isdir(curDir))
|
||||
mkdir(curDir);
|
||||
}
|
||||
while(srch != -1)
|
||||
}
|
||||
|
||||
// Writes an ini file.
|
||||
struct IniWriter
|
||||
{
|
||||
File ini;
|
||||
bool firstSection = true;
|
||||
|
||||
void openFile(char[] file)
|
||||
{
|
||||
if(ini is null) ini = new File();
|
||||
char[] oldFile = file~".old";
|
||||
|
||||
if(exists(file))
|
||||
copy(file, oldFile);
|
||||
|
||||
// Make sure the output directory exists
|
||||
char[] dr = getDirName(file);
|
||||
if(dr.length)
|
||||
safeMkdir(dr);
|
||||
|
||||
ini.open(file, FileMode.OutNew);
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
ini.close();
|
||||
}
|
||||
|
||||
void section(char[] name)
|
||||
{
|
||||
if(firstSection)
|
||||
firstSection = false;
|
||||
else
|
||||
ini.writefln();
|
||||
|
||||
ini.writefln("[%s]", name);
|
||||
}
|
||||
|
||||
void comment(char[] str)
|
||||
{
|
||||
ini.writefln("; %s", str);
|
||||
}
|
||||
|
||||
void writeType(T)(char[] name, T t)
|
||||
{
|
||||
ini.writefln("%s=%s", name, t);
|
||||
}
|
||||
|
||||
alias writeType!(int) writeInt;
|
||||
alias writeType!(char[]) writeString;
|
||||
|
||||
void writeFloat(char[] name, float f)
|
||||
{
|
||||
ini.writefln("%s=%.3s", name, f);
|
||||
}
|
||||
|
||||
void writeBool(char[] name, bool b)
|
||||
{
|
||||
ini.writefln("%s=%s", name, b?"yes":"no");
|
||||
}
|
||||
}
|
||||
|
||||
// Keytype that holds section name and variable name
|
||||
struct SecVar
|
||||
{
|
||||
private:
|
||||
char[] section;
|
||||
char[] variable;
|
||||
|
||||
public:
|
||||
|
||||
bool opEquals(ref SecVar v)
|
||||
{
|
||||
return section == v.section && variable == v.variable;
|
||||
}
|
||||
|
||||
uint toHash()
|
||||
{
|
||||
const auto ID = typeid(char[]);
|
||||
return ID.getHash(§ion) + ID.getHash(&variable);
|
||||
}
|
||||
|
||||
static SecVar opCall(char[] sec, char[] var)
|
||||
{
|
||||
SecVar sv;
|
||||
sv.section = sec;
|
||||
sv.variable = var;
|
||||
return sv;
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
{
|
||||
return "[" ~ section ~ "]:" ~ variable;
|
||||
}
|
||||
}
|
||||
|
||||
struct IniReader
|
||||
{
|
||||
private:
|
||||
HashTable!(SecVar, char[]) vars;
|
||||
char[] section;
|
||||
|
||||
// Start a new section
|
||||
void setSection(char[] sec)
|
||||
{
|
||||
section = sec.dup;
|
||||
}
|
||||
|
||||
// Insert a new value from file
|
||||
void set(char[] variable, char[] value)
|
||||
{
|
||||
vars[SecVar(section, variable.dup)] = value.dup;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void reset()
|
||||
{
|
||||
vars.reset();
|
||||
section = null;
|
||||
wasRead = false;
|
||||
}
|
||||
|
||||
int getInt(char[] sec, char[] var, int def)
|
||||
{
|
||||
char[] value;
|
||||
if(vars.inList(SecVar(sec,var), value))
|
||||
return cast(int)atoi(value);
|
||||
else
|
||||
return def;
|
||||
}
|
||||
|
||||
float getFloat(char[] sec, char[] var, float def)
|
||||
{
|
||||
char[] value;
|
||||
if(vars.inList(SecVar(sec,var), value))
|
||||
return atof(value);
|
||||
else
|
||||
return def;
|
||||
}
|
||||
|
||||
char[] getString(char[] sec, char[] var, char[] def)
|
||||
{
|
||||
char[] value;
|
||||
if(vars.inList(SecVar(sec,var), value))
|
||||
return value;
|
||||
else
|
||||
return def;
|
||||
}
|
||||
|
||||
// Return true if the string matches some case of 'yes', and false
|
||||
// otherwise.
|
||||
bool getBool(char[] sec, char[] var, bool def)
|
||||
{
|
||||
char[] value;
|
||||
if(vars.inList(SecVar(sec,var), value))
|
||||
return icmp(value, "yes") == 0;
|
||||
else
|
||||
return def;
|
||||
}
|
||||
|
||||
bool wasRead = false;
|
||||
|
||||
void readFile(char[] fn)
|
||||
{
|
||||
// Reset this struct
|
||||
reset();
|
||||
|
||||
// If the file doesn't exist, simply exit. This will work fine,
|
||||
// and default values will be used instead.
|
||||
if(!exists(fn)) return;
|
||||
|
||||
wasRead = true;
|
||||
|
||||
// Read buffer. Finite in size but perfectly safe - the readLine
|
||||
// routine allocates more mem if it needs it.
|
||||
char[300] buffer;
|
||||
|
||||
scope File ini = new File(fn);
|
||||
while(!ini.eof)
|
||||
{
|
||||
char[] line = ini.readLine(buffer);
|
||||
|
||||
// Remove leading and trailing whitespace
|
||||
line = strip(line);
|
||||
|
||||
// Ignore comments and blank lines
|
||||
if(line.length == 0 || line.begins(";")) continue;
|
||||
|
||||
// New section?
|
||||
if(line.begins("["))
|
||||
{
|
||||
if(!line.ends("]"))
|
||||
{
|
||||
//writefln("Malformed section: %s", line);
|
||||
continue;
|
||||
}
|
||||
|
||||
setSection(line[1..$-1]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split line into a key and a value
|
||||
int index = line.find('=');
|
||||
if(index != -1)
|
||||
{
|
||||
char[] value = line[index+1..$];
|
||||
line = line[0..index];
|
||||
set(line, value);
|
||||
}
|
||||
//else writefln("Malformed value: '%s'", line);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (memory.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module core.memory;
|
||||
|
||||
import util.regions;
|
||||
|
||||
// Global memory managers used by various loading routines
|
||||
RegionManager
|
||||
esmRegion, // Memory used by ESMs, BSAs and plugins. Cleared only
|
||||
// when all files are reloaded. Lifetime is current
|
||||
// plugin setting session - if we change the set of
|
||||
// plugins, this region is reset and everything is
|
||||
// reloaded.
|
||||
|
||||
gameRegion, // Memory used in current game. Cleared whenever a new
|
||||
// game is loaded. Lifetime is the current game
|
||||
// session, loading a saved game will reset the
|
||||
// region.
|
||||
|
||||
nifRegion; // Used by the NIF loader. Cleared when a NIF file has
|
||||
// been inserted into OGRE and the file data is no
|
||||
// longer needed. In other words this is cleared
|
||||
// immediately after loading each NIF file.
|
||||
|
||||
// AA allocator that uses esmRegion
|
||||
struct ESMRegionAlloc
|
||||
{
|
||||
static const bool autoinit = false;
|
||||
static void* alloc(uint size) { return esmRegion.allocate(size).ptr; }
|
||||
static void free(void* p) { }
|
||||
}
|
||||
|
||||
void initializeMemoryRegions()
|
||||
{
|
||||
// Default block sizes are probably ok
|
||||
esmRegion = new RegionManager("ESM");
|
||||
gameRegion = new RegionManager("GAME");
|
||||
nifRegion = new RegionManager("NIF");
|
||||
}
|
@ -1,508 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (resource.d) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module core.resource;
|
||||
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import std.stream;
|
||||
import std.file;
|
||||
import std.path;
|
||||
|
||||
import monster.util.aa;
|
||||
import monster.util.string;
|
||||
|
||||
import bsa.bsafile;
|
||||
|
||||
import bullet.bindings;
|
||||
|
||||
import core.memory;
|
||||
import core.config;
|
||||
|
||||
import ogre.bindings;
|
||||
import ogre.meshloader;
|
||||
|
||||
import sound.audio;
|
||||
import sound.sfx;
|
||||
|
||||
import nif.nif;
|
||||
|
||||
import core.filefinder;
|
||||
|
||||
// These are handles for various resources. They may refer to a file
|
||||
// in the file system, an entry in a BSA archive, or point to an
|
||||
// already loaded resource. Resource handles that are not implemented
|
||||
// yet are typedefed as ints for the moment.
|
||||
typedef int IconIndex;
|
||||
|
||||
alias SoundResource* SoundIndex;
|
||||
alias TextureResource* TextureIndex;
|
||||
alias MeshResource* MeshIndex;
|
||||
|
||||
ResourceManager resources;
|
||||
|
||||
// Called from ogre/cpp_bsaarchive.cpp. We will probably move these
|
||||
// later.
|
||||
extern(C)
|
||||
{
|
||||
// Does the file exist in the archives?
|
||||
int d_bsaExists(char *filename)
|
||||
{
|
||||
char[] name = toString(filename);
|
||||
|
||||
auto res = resources.lookupTexture(name);
|
||||
if(res.bsaFile != -1)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Open a file. Return the pointer and size.
|
||||
void d_bsaOpenFile(char *filename,
|
||||
void **retPtr, uint *retSize)
|
||||
{
|
||||
char[] name = toString(filename);
|
||||
void[] result;
|
||||
|
||||
//writefln("calling d_bsaOpenFile(%s, %s)", bsaFile, name);
|
||||
|
||||
auto tex = resources.lookupTexture(name);
|
||||
|
||||
if(tex.bsaFile == -1) result = null;
|
||||
else result = resources.archives[tex.bsaFile].findSlice(tex.bsaIndex);
|
||||
*retPtr = result.ptr;
|
||||
*retSize = result.length;
|
||||
}
|
||||
}
|
||||
|
||||
struct ResourceManager
|
||||
{
|
||||
private:
|
||||
// Holds lists of resources in the file system.
|
||||
FileFinder
|
||||
meshes,
|
||||
icons,
|
||||
textures,
|
||||
sounds,
|
||||
bookart,
|
||||
bsa, esm, esp;
|
||||
|
||||
// Archives
|
||||
BSAFile archives[];
|
||||
|
||||
// List of resources that have already been looked up (case
|
||||
// insensitive)
|
||||
HashTable!(char[], MeshIndex, ESMRegionAlloc, CITextHash) meshLookup;
|
||||
HashTable!(char[], TextureIndex, ESMRegionAlloc, CITextHash) textureLookup;
|
||||
HashTable!(char[], SoundIndex, ESMRegionAlloc, CITextHash) soundLookup;
|
||||
|
||||
public:
|
||||
|
||||
// Hack. Set to true in esmtool to disable all the lookup*
|
||||
// functions.
|
||||
bool dummy = false;
|
||||
|
||||
void initResources()
|
||||
{
|
||||
bsa = new FileFinder(config.bsaDir, "bsa", Recurse.No);
|
||||
archives.length = bsa.length;
|
||||
foreach(int i, ref BSAFile f; archives)
|
||||
f = new BSAFile(bsa[i+1]);
|
||||
|
||||
sounds = new FileFinder(config.sndDir);
|
||||
|
||||
// Simple playlists, similar to the Morrowind one. Later I imagine
|
||||
// adding an interactive MP3 player with a bit more finesse, but
|
||||
// this will do for now.
|
||||
char[][] music;
|
||||
|
||||
char[][] getDir(char[] dir)
|
||||
{
|
||||
dir = FileFinder.addSlash(dir);
|
||||
char[][] res = ((exists(dir) && isdir(dir)) ? listdir(dir) : null);
|
||||
foreach(ref char[] fn; res)
|
||||
fn = dir ~ fn;
|
||||
return res;
|
||||
}
|
||||
|
||||
Music.setPlaylists(getDir(config.musDir),
|
||||
getDir(config.musDir2));
|
||||
|
||||
meshLookup.reset();
|
||||
textureLookup.reset();
|
||||
soundLookup.reset();
|
||||
|
||||
meshBuffer[0..7] = "meshes\\";
|
||||
texBuffer[0..9] = "textures\\";
|
||||
}
|
||||
|
||||
// These three functions are so similar that I should probably split
|
||||
// out big parts of them into one common function.
|
||||
SoundIndex lookupSound(char[] id)
|
||||
{
|
||||
if(dummy) return null;
|
||||
|
||||
assert(id != "", "loadSound called with empty id");
|
||||
|
||||
SoundIndex si;
|
||||
|
||||
if( soundLookup.inList(id, si) ) return si;
|
||||
|
||||
si = esmRegion.newT!(SoundResource);
|
||||
|
||||
// Check if the file exists
|
||||
int index = sounds[id];
|
||||
|
||||
// If so, get the real file name
|
||||
if(index) si.file = sounds[index];
|
||||
// Otherwise, make this an empty resource
|
||||
else
|
||||
{
|
||||
//writefln("Lookup failed to find sound %s", id);
|
||||
si.file = null;
|
||||
}
|
||||
|
||||
si.res.loaded = false;
|
||||
|
||||
// Copy name and insert. We MUST copy here, since indices during
|
||||
// load are put in a temporary buffer, and thus overwritten.
|
||||
si.name = esmRegion.copyz(id);
|
||||
assert(si.name == id);
|
||||
soundLookup[si.name] = si;
|
||||
|
||||
return si;
|
||||
}
|
||||
|
||||
// Quick but effective hack
|
||||
char[80] meshBuffer;
|
||||
|
||||
MeshIndex lookupMesh(char[] id)
|
||||
{
|
||||
if(dummy) return null;
|
||||
|
||||
MeshIndex mi;
|
||||
|
||||
// If it is already looked up, return the result
|
||||
if( meshLookup.inList(id, mi) ) return mi;
|
||||
|
||||
mi = esmRegion.newT!(MeshResource);
|
||||
|
||||
// If not, find it. For now we only check the BSA.
|
||||
char[] search;
|
||||
if(id.length < 70)
|
||||
{
|
||||
// Go to great lengths to avoid the concat :) The speed gain
|
||||
// is negligible (~ 1%), but the GC memory usage is HALVED!
|
||||
// This may mean significantly fewer GC collects during a long
|
||||
// run of the program.
|
||||
meshBuffer[7..7+id.length] = id;
|
||||
search = meshBuffer[0..7+id.length];
|
||||
}
|
||||
else
|
||||
search = "meshes\\" ~ id;
|
||||
|
||||
//writefln("lookupMesh(%s): searching for %s", id, search);
|
||||
|
||||
mi.bsaIndex = -1;
|
||||
mi.bsaFile = -1;
|
||||
foreach(int ind, BSAFile bs; archives)
|
||||
{
|
||||
mi.bsaIndex = bs.getIndex(search);
|
||||
if(mi.bsaIndex != -1) // Found something
|
||||
{
|
||||
mi.bsaFile = ind;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(mi.bsaIndex == -1)
|
||||
{
|
||||
//writefln("Lookup failed to find mesh %s", search);
|
||||
assert(mi.bsaFile == -1);
|
||||
}
|
||||
|
||||
// Resource is not loaded
|
||||
mi.node = null;
|
||||
|
||||
// Make a copy of the id
|
||||
mi.name = esmRegion.copyz(id);
|
||||
meshLookup[mi.name] = mi;
|
||||
|
||||
return mi;
|
||||
}
|
||||
|
||||
char[80] texBuffer;
|
||||
|
||||
// Checks the BSAs / file system for a given texture name. Tries
|
||||
// various Morrowind-specific hacks, like changing the extension to
|
||||
// .dds and adding a 'textures\' prefix. Does not load the texture.
|
||||
TextureIndex lookupTexture(char[] id)
|
||||
{
|
||||
if(dummy) return null;
|
||||
|
||||
TextureIndex ti;
|
||||
|
||||
// Checked if we have looked it up before
|
||||
if( textureLookup.inList(id, ti) ) return ti;
|
||||
|
||||
// Create a new resource locator
|
||||
ti = esmRegion.newT!(TextureResource);
|
||||
|
||||
ti.name = esmRegion.copyz(id);
|
||||
ti.newName = ti.name;
|
||||
ti.type = ti.name[$-3..$];
|
||||
|
||||
void searchBSAs(char[] search)
|
||||
{
|
||||
// Look it up in the BSA
|
||||
ti.bsaIndex = -1;
|
||||
ti.bsaFile = -1;
|
||||
foreach(int ind, BSAFile bs; archives)
|
||||
{
|
||||
ti.bsaIndex = bs.getIndex(search);
|
||||
if(ti.bsaIndex != -1) // Found something
|
||||
{
|
||||
ti.bsaFile = ind;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void searchWithDDS(char[] search)
|
||||
{
|
||||
searchBSAs(search);
|
||||
|
||||
// If we can't find it, try the same filename but with .dds as
|
||||
// the extension. Bethesda did at some point convert all their
|
||||
// textures to dds to improve loading times. However, they did
|
||||
// not update their esm-files or require them to use the
|
||||
// correct extention (if they had, it would have broken a lot
|
||||
// of user mods). So we must support files that are referenced
|
||||
// as eg .tga but stored as .dds.
|
||||
if(ti.bsaIndex == -1 && ti.type != "dds")
|
||||
{
|
||||
search[$-3..$] = "dds";
|
||||
searchBSAs(search);
|
||||
if(ti.bsaIndex != -1)
|
||||
{
|
||||
// Store the real name in newName.
|
||||
ti.newName = esmRegion.copyz(ti.name);
|
||||
|
||||
// Get a slice of the extension and overwrite it.
|
||||
ti.type = ti.newName[$-3..$];
|
||||
ti.type[] = "dds";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search for 'texture\name' first
|
||||
char[] tmp;
|
||||
if(id.length < 70)
|
||||
{
|
||||
// Avoid memory allocations if possible.
|
||||
tmp = texBuffer[0..9+id.length];
|
||||
tmp[9..$] = id;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmp = "textures\\" ~ id;
|
||||
writefln("WARNING: Did an allocation on %s", tmp);
|
||||
}
|
||||
|
||||
searchWithDDS(tmp);
|
||||
|
||||
// Not found? Try without the 'texture\'
|
||||
if(ti.bsaIndex == -1)
|
||||
{
|
||||
tmp = tmp[9..$];
|
||||
tmp[] = id; // Reset the name (replace .dds with the original)
|
||||
|
||||
searchWithDDS(tmp);
|
||||
}
|
||||
|
||||
// Check that extensions match, to be on the safe side
|
||||
assert(ti.type == ti.newName[$-3..$]);
|
||||
|
||||
if(ti.bsaIndex == -1)
|
||||
{
|
||||
//writefln("Lookup failed to find texture %s", tmp);
|
||||
assert(ti.bsaFile == -1);
|
||||
}
|
||||
|
||||
textureLookup[ti.name] = ti;
|
||||
|
||||
return ti;
|
||||
}
|
||||
|
||||
IconIndex lookupIcon(char[] id) { return -3; }
|
||||
|
||||
// Inserts a given mesh into ogre. Currently only reads the BSA
|
||||
// file. Is only called from within MeshResource itself, and should
|
||||
// never be called when the mesh is already loaded.
|
||||
private void loadMesh(MeshIndex mi)
|
||||
in
|
||||
{
|
||||
assert(!mi.isLoaded);
|
||||
assert(!mi.isEmpty);
|
||||
}
|
||||
body
|
||||
{
|
||||
// Get a slice of the mesh
|
||||
void[] s = archives[mi.bsaFile].findSlice(mi.bsaIndex);
|
||||
|
||||
// Load the NIF into memory. No need to call close(), the file is
|
||||
// automatically closed when the data is loaded.
|
||||
nifMesh.open(s, mi.name);
|
||||
|
||||
// Load and insert nif
|
||||
// TODO: Might add BSA name to the handle name, for clarity
|
||||
meshLoader.loadMesh(mi.name, mi.node, mi.shape);
|
||||
|
||||
// TODO: We could clear the BSA memory mapping here to free some
|
||||
// mem
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct SoundResource
|
||||
{
|
||||
private:
|
||||
char[] name;
|
||||
char[] file;
|
||||
SoundFile res;
|
||||
|
||||
public:
|
||||
char[] getName() { return name; }
|
||||
|
||||
SoundInstance getInstance()
|
||||
in
|
||||
{
|
||||
assert(!isEmpty());
|
||||
}
|
||||
body
|
||||
{
|
||||
if(!isLoaded())
|
||||
res.load(file);
|
||||
|
||||
return res.getInstance();
|
||||
}
|
||||
|
||||
bool isEmpty() { return file == ""; }
|
||||
bool isLoaded() { return res.loaded; }
|
||||
}
|
||||
|
||||
struct MeshResource
|
||||
{
|
||||
private:
|
||||
char[] name;
|
||||
int bsaFile;
|
||||
int bsaIndex;
|
||||
|
||||
// Points to the 'template' SceneNode of this mesh. Is null if this
|
||||
// mesh hasn't been inserted yet.
|
||||
NodePtr node;
|
||||
|
||||
public:
|
||||
|
||||
// Bullet collision shape. Can be null.
|
||||
BulletShape shape;
|
||||
|
||||
NodePtr getNode()
|
||||
in
|
||||
{
|
||||
assert(!isEmpty());
|
||||
}
|
||||
body
|
||||
{
|
||||
if(node == null) resources.loadMesh(this);
|
||||
return node;
|
||||
}
|
||||
|
||||
char[] getName() { return name; }
|
||||
|
||||
// Returns true if this resource does not exist (ie. file not found)
|
||||
// TODO: This must be modified later for non-BSA files.
|
||||
bool isEmpty()
|
||||
{
|
||||
return bsaIndex == -1;
|
||||
}
|
||||
|
||||
// Returns true if resource is loaded
|
||||
bool isLoaded()
|
||||
{
|
||||
return node != null;
|
||||
}
|
||||
}
|
||||
|
||||
struct TextureResource
|
||||
{
|
||||
private:
|
||||
char[] name;
|
||||
char[] newName; // Converted name, ie. with extension converted to
|
||||
// .dds if necessary
|
||||
int bsaFile; // If set to -1, the file is in the file system
|
||||
int bsaIndex;
|
||||
char[] type; // Texture format, eg "tga" or "dds";
|
||||
|
||||
public:
|
||||
|
||||
char[] getName() { return name; }
|
||||
char[] getNewName() { return newName; }
|
||||
|
||||
// Returns true if this resource does not exist (ie. file not found)
|
||||
bool isEmpty()
|
||||
{
|
||||
return bsaIndex == -1;
|
||||
}
|
||||
}
|
||||
|
||||
// OLD STUFF
|
||||
/+
|
||||
|
||||
void initResourceManager()
|
||||
{
|
||||
// Find all resource files
|
||||
char[] morroDir = config.morrowindDirectory();
|
||||
bsa = new FileFinder(morroDir, "bsa");
|
||||
|
||||
// Jump to the data files directory
|
||||
morroDir = config.dataFilesDirectory();
|
||||
|
||||
esm = new FileFinder(morroDir, "esm", Recurse.No);
|
||||
esp = new FileFinder(morroDir, "esp", Recurse.No);
|
||||
|
||||
meshes = new FileFinder(morroDir ~ "Meshes");
|
||||
icons = new FileFinder(morroDir ~ "Icons");
|
||||
textures = new FileFinder(morroDir ~ "Textures");
|
||||
sounds = new FileFinder(morroDir ~ "Sound");
|
||||
bookart = new FileFinder(morroDir ~ "BookArt");
|
||||
|
||||
char[][] bsas = config.bsaArchives();
|
||||
archives.length = bsas.length;
|
||||
writef("Loading BSA archives...");
|
||||
|
||||
writefln(" Done\n");
|
||||
}
|
||||
}
|
||||
+/
|
File diff suppressed because it is too large
Load Diff
@ -1,165 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (block.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.compiler.block;
|
||||
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.assembler;
|
||||
|
||||
import monster.vm.error;
|
||||
|
||||
// Base class for all kinds of blocks. A 'block' is a token or
|
||||
// collection of tokens that belong together and form a syntactical
|
||||
// unit. A block might for example be a statement, a declaration, a
|
||||
// block of code, or the entire class.
|
||||
abstract class Block
|
||||
{
|
||||
protected:
|
||||
static Token next(ref TokenArray toks)
|
||||
{
|
||||
if(toks.length == 0)
|
||||
fail("Unexpected end of file");
|
||||
Token tt = toks[0];
|
||||
toks = toks[1..toks.length];
|
||||
return tt;
|
||||
}
|
||||
|
||||
static Floc getLoc(TokenArray toks)
|
||||
{
|
||||
Floc loc;
|
||||
if(toks.length) loc = toks[0].loc;
|
||||
return loc;
|
||||
}
|
||||
|
||||
static bool isNext(ref TokenArray toks, TT type)
|
||||
{
|
||||
Floc ln;
|
||||
return isNext(toks, type, ln);
|
||||
}
|
||||
|
||||
static bool isNext(ref TokenArray toks, TT type, out Floc loc)
|
||||
{
|
||||
if( toks.length == 0 ) return false;
|
||||
loc = toks[0].loc;
|
||||
if( toks[0].type != type ) return false;
|
||||
next(toks);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isNext(ref TokenArray toks, TT type, out Token tok)
|
||||
{
|
||||
if( toks.length == 0 ) return false;
|
||||
if( toks[0].type != type ) return false;
|
||||
tok = next(toks);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Is the next token a separator, ie. a ; or a new line
|
||||
static bool isSep(ref TokenArray toks, TT symbol = TT.Semicolon)
|
||||
{
|
||||
if( toks.length == 0 ) return true;
|
||||
if( toks[0].newline ) return true;
|
||||
return isNext(toks, symbol);
|
||||
}
|
||||
|
||||
// Returns true if the next token is on a new line. Does not remove
|
||||
// any tokens.
|
||||
static bool isNewline(ref TokenArray toks)
|
||||
// TT.EMPTY never occurs in the stream, so it's safe to check for
|
||||
// it.
|
||||
{ return isSep(toks, TT.EMPTY); }
|
||||
|
||||
// Require either a line break or a given character (default ;)
|
||||
static void reqSep(ref TokenArray toks, TT symbol = TT.Semicolon)
|
||||
{
|
||||
if(!isSep(toks, symbol))
|
||||
fail("Expected '" ~ tokenList[symbol] ~ "' or newline", toks);
|
||||
}
|
||||
|
||||
static void reqNext(ref TokenArray toks, TT type, out Token tok)
|
||||
{
|
||||
if(!isNext(toks, type, tok))
|
||||
fail("Expected " ~ tokenList[type], toks);
|
||||
}
|
||||
|
||||
static void reqNext(ref TokenArray toks, TT type, out Floc loc)
|
||||
{
|
||||
Token t;
|
||||
reqNext(toks, type, t);
|
||||
loc = t.loc;
|
||||
}
|
||||
|
||||
static void reqNext(ref TokenArray toks, TT type)
|
||||
{
|
||||
Token t;
|
||||
reqNext(toks, type, t);
|
||||
}
|
||||
|
||||
// Skip any matching set of parens (), [] or {}, and anything inside
|
||||
// it.
|
||||
static void skipParens(ref TokenArray toks, TT type = TT.LeftParen)
|
||||
{
|
||||
TT endType;
|
||||
if(type == TT.LeftParen)
|
||||
endType = TT.RightParen;
|
||||
else if(type == TT.LeftCurl)
|
||||
endType = TT.RightCurl;
|
||||
else if(type == TT.LeftSquare)
|
||||
endType = TT.RightSquare;
|
||||
else assert(0);
|
||||
|
||||
int count = 0;
|
||||
|
||||
while(toks.length != 0)
|
||||
{
|
||||
if(toks[0].type == type)
|
||||
count++;
|
||||
else if(toks[0].type == endType)
|
||||
count--;
|
||||
|
||||
toks = toks[1..$];
|
||||
|
||||
if(count <= 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the assembler debug line to the line belonging to this
|
||||
// block.
|
||||
final void setLine() { tasm.setLine(loc.line); }
|
||||
|
||||
public:
|
||||
// File position where this block was defined
|
||||
Floc loc;
|
||||
|
||||
// Parse a list of tokens and attempt to understand how they belong
|
||||
// together. This is the syntactical level.
|
||||
void parse(ref TokenArray toks);
|
||||
|
||||
// This goes through the code, resolves names and types etc,
|
||||
// converts expressions into an intermediate form which can be
|
||||
// compiled to byte code later. This is basically the semantic level.
|
||||
void resolve(Scope sc);
|
||||
}
|
@ -1,656 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (bytecode.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.compiler.bytecode;
|
||||
|
||||
// Byte code operands
|
||||
enum BC
|
||||
{
|
||||
Exit = 1, // Exit function.
|
||||
|
||||
Call, // Call function in this object. Takes a
|
||||
// global function index on the stack, int.
|
||||
|
||||
CallFar, // Call function in another object. The object
|
||||
// must be pushed on the stack, followed by
|
||||
// the global function index.
|
||||
|
||||
Return, // Takes a parameter nr (int). Equivalent to:
|
||||
// POPN nr (remove nr values of the stack)
|
||||
// EXIT
|
||||
|
||||
ReturnVal, // Takes parameter nr (int). Equivalent to:
|
||||
// POP value and store it in TMP
|
||||
// POPN nr (remove function parameters)
|
||||
// PUSH TMP (put return value back on stack)
|
||||
// EXIT
|
||||
|
||||
ReturnValN, // Same as ReturnVal, but takes a second
|
||||
// parameter (int). This gives the size of the
|
||||
// return value.
|
||||
|
||||
State, // Set state. Followed by an int giving the
|
||||
// state index, another int giving the label
|
||||
// index and a third int giving the class tree
|
||||
// index. State index -1 means the empty
|
||||
// state, and -1 for the label means no label
|
||||
// is specified (eg state = test; instead of
|
||||
// state = test.label). For state -1 the label
|
||||
// index must also be -1, and the class index
|
||||
// is ignored.
|
||||
|
||||
EnumValue, // Get the 'value' field of the enum variable
|
||||
// on the stack. Takes an enum type index.
|
||||
|
||||
EnumField, // Get the given field of an enum. Takes the
|
||||
// field number and the enum type index, both
|
||||
// ints.
|
||||
|
||||
EnumValToIndex, // Used to look up enum names (string index)
|
||||
EnumNameToIndex, // or value (long) and returns the enum index.
|
||||
|
||||
|
||||
New, // Create a new object. Followed by an int
|
||||
// giving the class index (in the file lookup
|
||||
// table) and one giving the paramter
|
||||
// number. The parameter indices, class
|
||||
// indices and values are popped of the stack,
|
||||
// see NewExpression.evalAsm for details.
|
||||
|
||||
Clone, // Clones an object - create a new object of
|
||||
// the same class, then copy variable values
|
||||
// and state from the old object to the
|
||||
// new. Replaces the object index on the stack
|
||||
// with the new index.
|
||||
|
||||
Jump, // Jump to given position (int)
|
||||
|
||||
JumpZ, // Pop a value, if it is zero then jump to
|
||||
// given position (int)
|
||||
|
||||
JumpNZ, // Jump if non-zero
|
||||
|
||||
PushData, // Push the next four bytes verbatim
|
||||
|
||||
PushLocal, // Push value of a local variable or function
|
||||
// parameter, index given as a signed
|
||||
// int. Gives the index from the current stack
|
||||
// frame. 0 is the first int, 1 is the second
|
||||
// int, -1 is the last int in the function
|
||||
// parameter area, etc.
|
||||
|
||||
PushClassVar, // Push value in object data segment (in this
|
||||
// object). Parameter is an int offset.
|
||||
|
||||
PushParentVar, // Push value in data segment of parent
|
||||
// object. int class tree index, int offset
|
||||
|
||||
PushFarClassVar, // Push value from the data segment in another
|
||||
// object. The object reference is already on
|
||||
// the stack. int class tree index, int offset
|
||||
|
||||
PushFarClassMulti, // Pushes multiple ints from the data
|
||||
// segment. Takes the variable size (int) as
|
||||
// the first parameter, otherwise identical to
|
||||
// PushFarClassVar.
|
||||
|
||||
PushThis, // Push the 'this' object reference
|
||||
|
||||
PushSingleton, // Push the singleton object. Takes the global
|
||||
// class index (int) as parameter.
|
||||
|
||||
// The Push*8 instructions are not implemented yet as they are
|
||||
// just optimizations of existing features. The names are reserved
|
||||
// for future use.
|
||||
|
||||
Push8, // Push the next 8 bytes (two ints)
|
||||
// verbatim.
|
||||
PushLocal8, // Does the same as PushLocal except it also
|
||||
// pushes the next int onto the stack. The
|
||||
// index must be > 0 or <= -2.
|
||||
PushClassVar8, // Same as PushClassVar but pushes two ints
|
||||
PushFarClassVar8,
|
||||
|
||||
|
||||
Pop, // Pop data and forget about it
|
||||
|
||||
PopN, // Takes a byte parameter N, equivalent to
|
||||
// calling Pop N times.
|
||||
|
||||
Dup, // Duplicate the next value on the
|
||||
// stack. Equivalent to: a=pop; push a; push
|
||||
// a;
|
||||
|
||||
Store, // Basic operation for moving data to
|
||||
// memory. Schematically pops a Ptr of the
|
||||
// stack, pops a value, moves the value into
|
||||
// the Ptr.
|
||||
|
||||
Store8, // Same as Store except two ints are popped
|
||||
// from the stack and moved into the data.
|
||||
|
||||
StoreMult, // Takes the size as an int parameter
|
||||
|
||||
IAdd, // Standard addition, operates on the two next
|
||||
// ints in the stack, and stores the result in
|
||||
// the stack.
|
||||
|
||||
ISub, // Subtraction. a-b, where a is pushed first.
|
||||
|
||||
IMul, // Multiplication
|
||||
|
||||
IDiv, // Division. The dividend must be pushed
|
||||
// first, the divisor second.
|
||||
UDiv, // uint
|
||||
|
||||
IDivRem, // Reminder division
|
||||
UDivRem, // uint
|
||||
|
||||
INeg, // Negate the next integer on the stack
|
||||
|
||||
FAdd, // Float arithmetic
|
||||
FSub,
|
||||
FMul,
|
||||
FDiv,
|
||||
FIDiv,
|
||||
FDivRem,
|
||||
FNeg,
|
||||
|
||||
LAdd, // Long arithmetic
|
||||
LSub,
|
||||
LMul,
|
||||
LDiv,
|
||||
ULDiv,
|
||||
LDivRem,
|
||||
ULDivRem,
|
||||
LNeg,
|
||||
|
||||
DAdd, // Double arithmetic
|
||||
DSub,
|
||||
DMul,
|
||||
DDiv,
|
||||
DIDiv,
|
||||
DDivRem,
|
||||
DNeg,
|
||||
|
||||
IsEqual, // Pop two ints, push true (1) if they are
|
||||
// equal or false (0) otherwise
|
||||
IsEqualMulti, // Takes an int parameter giving the value size
|
||||
|
||||
IsCaseEqual, // Same as IsEqual, but uses (unicode) case
|
||||
// insensitive match. Values are assumed to be
|
||||
// dchars.
|
||||
|
||||
CmpArray, // Compare two arrays. Only does exact
|
||||
// byte-for-byte matching.
|
||||
ICmpStr, // Does a case insensitive check of two
|
||||
// strings. Both these pop two array indices
|
||||
// of the stack and push a bool.
|
||||
|
||||
PreInc, // ++, pops an address, increases variable,
|
||||
// and pushes the value
|
||||
|
||||
PreDec, // -- (these might change)
|
||||
|
||||
PostInc, // ++ and -- that push the original value
|
||||
PostDec, // rather than the new one
|
||||
|
||||
PreInc8, PreDec8,
|
||||
PostInc8, PostDec8, // 64 bit versions
|
||||
|
||||
Not, // Inverse the bool on the stack
|
||||
|
||||
ILess, // Pops b, pops a, pushes true if a < b, false
|
||||
// otherwise. Works on signed ints.
|
||||
|
||||
ULess, // unsigned ints
|
||||
LLess, // long
|
||||
ULLess, // ulong
|
||||
FLess, // float
|
||||
DLess, // double
|
||||
|
||||
// All the Cast* instructions pops a variable of the given type of
|
||||
// the stack, and pushes back an equivalent variable of the new
|
||||
// type.
|
||||
|
||||
CastI2L, // int to long (signed)
|
||||
|
||||
CastD2F, // double to float
|
||||
CastF2D, // float to double
|
||||
|
||||
CastI2F, // int to float
|
||||
CastU2F, // uint to float
|
||||
CastL2F, // long to float
|
||||
CastUL2F, // ulong to float
|
||||
|
||||
CastI2D, // int to double
|
||||
CastU2D, // uint to double
|
||||
CastL2D, // long to double
|
||||
CastUL2D, // ulong to double
|
||||
|
||||
CastF2I, // float to int
|
||||
CastF2U, // float to uint
|
||||
CastF2L, // float to long
|
||||
CastF2UL, // float to ulong
|
||||
|
||||
CastD2I, // double to int
|
||||
CastD2U, // double to uint
|
||||
CastD2L, // double to long
|
||||
CastD2UL, // double to ulong
|
||||
|
||||
CastS2C, // string to char - string must have length 1
|
||||
|
||||
CastT2S, // cast any type to string. Takes the type
|
||||
// index (int) as a parameter
|
||||
|
||||
DownCast, // Takes a global class index (int). Checks if
|
||||
// the object on the stack is an instance of
|
||||
// the given class.
|
||||
|
||||
RefFunc, // Pop an array reference (two ints) and push
|
||||
// the corresponding function name as a string
|
||||
|
||||
FetchElem, // Get an element from an array. Pops the
|
||||
// index, then the array reference, then
|
||||
// pushes the value. The element size is
|
||||
// determined from the array.
|
||||
|
||||
GetArrLen, // Get the length of an array. Pops the array,
|
||||
// pushes the length.
|
||||
|
||||
PopToArray, // Takes a raw length n (int) and an element
|
||||
// size (int). Creates an array from the last
|
||||
// n values on the stack and pops them off.
|
||||
// Pushes the new array index. The values are
|
||||
// copied into the new array, and are
|
||||
// independent of the stack.
|
||||
|
||||
NewArray, // Takes one int giving the array nesting
|
||||
// level (rank), one int giving the element
|
||||
// size (s) and s ints giving the initial
|
||||
// value. Pops the lengths (one int per rank
|
||||
// level) from the stack. Pushes a new array
|
||||
// of the given length. The lengths should
|
||||
// pushed in the same order they appear in a
|
||||
// new-expression, ie. new int[1][2] are
|
||||
// pushed as 1 then 2.
|
||||
|
||||
CopyArray, // Pops two array indices from the stack, and
|
||||
// copies the data from one to another. The
|
||||
// destination array is popped first, then the
|
||||
// source. The lengths must match. The arrays
|
||||
// may overlap in memory without unexpected
|
||||
// effects.
|
||||
|
||||
DupArray, // Pops an array index of the stack, creates a
|
||||
// copy of the array, and pushes the index of
|
||||
// the new array.
|
||||
|
||||
MakeConstArray, // Pops an array index, creates a const
|
||||
// reference to the same data, pushes the
|
||||
// index.
|
||||
|
||||
IsConstArray, // Pops the array index, pushes bool
|
||||
// reflecting the const status
|
||||
|
||||
Slice, // Create a slice. Pops the second index, then
|
||||
// the first, then the array index. Pushes a
|
||||
// new array that is a slice of the original.
|
||||
|
||||
FillArray, // Fill an array. Pop an array index, then a
|
||||
// value. Sets all the elements in the array
|
||||
// to the value. Takes an int specifying the
|
||||
// element/value size.
|
||||
|
||||
CatArray, // Concatinate two arrays, on the stack.
|
||||
|
||||
CatLeft, // Concatinate an array with a left
|
||||
// element. The element is pushed first, so
|
||||
// the array index is first off the stack.
|
||||
CatRight, // Concatinate with right element. Array
|
||||
// pushed first, then element. Both these take
|
||||
// an element size (int).
|
||||
|
||||
ReverseArray, // Reverses an array. The index is on the
|
||||
// stack. The array is reversed in place, and
|
||||
// the index is left untouched on the stack.
|
||||
|
||||
CreateArrayIter, // Create array iterator. Expects the stack to
|
||||
// hold an index(int), a value(int) and an
|
||||
// array index, pushed in that order. Replaces
|
||||
// the array index with an iterator index,
|
||||
// sets the rest to reflect the first element
|
||||
// in iteration. Takes two byte parameters
|
||||
// that are either 0 or 1. If the first is
|
||||
// set, then the array is iterated in the
|
||||
// reverse order. If the second is set, then
|
||||
// the value is a reference, ie. changes to it
|
||||
// will be transfered back to the orignal
|
||||
// array. Pushes false if the array is empty,
|
||||
// true otherwise.
|
||||
|
||||
CreateClassIter, // Create a class iterator. Expects the
|
||||
// stack to hold a value (object index =
|
||||
// int). Takes one int parameter, which is the
|
||||
// class index. Pushes false if no objects
|
||||
// exist, true otherwise.
|
||||
|
||||
IterNext, // Iterate to the next element. Leaves the
|
||||
// iteration variables on the stack. Pushes
|
||||
// false if this was the last element, true
|
||||
// otherwise.
|
||||
|
||||
IterBreak, // Break off iteration. Does exactly the same
|
||||
// thing as IterNext would done if it had just
|
||||
// finished the last iteration step, except it
|
||||
// does not push anything onto the stack.
|
||||
|
||||
IterUpdate, // Update the original array or data from
|
||||
// reference variables in this
|
||||
// iteration. Called whenever a 'ref' variable
|
||||
// is changed, to make sure that the changes
|
||||
// take effect. Takes an int parameter that
|
||||
// gives the stack position (in the current
|
||||
// frame) of the iterator index.
|
||||
|
||||
GetStack, // Internal debugging function. Pushes the
|
||||
// current stack position on the stack. Might
|
||||
// be removed later.
|
||||
|
||||
MultiByte, // This instruction consists of a second byte
|
||||
// or an extra int. Reserved for future
|
||||
// use. This is intended for when / if the
|
||||
// number of instructions exceeds
|
||||
// 256. Instructions in this list with numbers
|
||||
// >= 256 will be coded in this way.
|
||||
|
||||
// Instructions appearing after the MultiByte mark might be coded
|
||||
// as multi-byte. They are handled in a more time-consuming
|
||||
// matter. You should only use this space for instructions that
|
||||
// are seldomly executed.
|
||||
|
||||
Error, // Throw an exception. Takes an error code
|
||||
// (byte) defined below. The library user will
|
||||
// later be able to choose whether this halts
|
||||
// execution entirely or just kills the
|
||||
// offending vthread.
|
||||
|
||||
Last
|
||||
}
|
||||
|
||||
// Make sure all single-byte instructions will in fact fit in a single
|
||||
// byte.
|
||||
static assert(BC.MultiByte < 255);
|
||||
|
||||
// Make us aware when we break the byte barrier
|
||||
static assert(BC.Last < 255);
|
||||
|
||||
enum Err
|
||||
{
|
||||
None, // Should never happen
|
||||
NoReturn, // Function is missing a return statement
|
||||
}
|
||||
|
||||
// Used for coded pointers. The first byte in a coded pointer gives
|
||||
// the pointer type, and the remaining 24 bits gives an index whose
|
||||
// meaning is determined by the type. The pointers can be used both
|
||||
// for variables and for functions. All pointers are 3 ints in size on
|
||||
// the stack. The comments below defines what the indices mean - the
|
||||
// ones that are not mentioned are zero.
|
||||
enum PT
|
||||
{
|
||||
Null = 0, // Null pointer. The index must also be zero.
|
||||
|
||||
Stack = 1, // Index is relative to function stack
|
||||
// frame. Used for local variables.
|
||||
|
||||
DataOffs = 2, // This class, this object. Index is data
|
||||
// segment offset.
|
||||
|
||||
DataOffsCls = 4, // Variable is in this object, but in another
|
||||
// class. The class MUST be a parent class of the
|
||||
// current object. A class tree index follows
|
||||
// this pointer on the stack.
|
||||
|
||||
FarDataOffs = 5, // Another class, another object. The index is a
|
||||
// data offset. Pop the class index off the
|
||||
// stack, and then object index.
|
||||
|
||||
ArrayIndex = 30, // Pointer to an array element. The array and
|
||||
// the index are pushed on the stack, the
|
||||
// pointer index is zero.
|
||||
}
|
||||
|
||||
char[] errorString(ubyte er)
|
||||
{
|
||||
if(er < errorToString.length)
|
||||
return errorToString[er];
|
||||
return "Unknown error code";
|
||||
}
|
||||
|
||||
union _CodePtr
|
||||
{
|
||||
// How the pointer is coded
|
||||
align(1) struct
|
||||
{
|
||||
ubyte type;
|
||||
int val24;
|
||||
}
|
||||
|
||||
// The end result is stored in val32
|
||||
align(1) struct
|
||||
{
|
||||
int val32;
|
||||
ubyte remains;
|
||||
}
|
||||
}
|
||||
static assert(_CodePtr.sizeof == 5);
|
||||
|
||||
// Encode a "pointer". Pointers are two shorts encoded into an
|
||||
// int. The first byte is the pointer type, the remaining 24 bits
|
||||
// gives the index.
|
||||
int codePtr(PT type, int index)
|
||||
{
|
||||
assert(index >= -(1<<23) && index < (1<<24),
|
||||
"index out of range for 24 bit value");
|
||||
assert(type != 0 || index == 0,
|
||||
"null pointers must have index == 0");
|
||||
assert(type == PT.Stack || index >= 0,
|
||||
"only PT.Stack can have a negative index");
|
||||
|
||||
|
||||
_CodePtr t;
|
||||
t.type = type;
|
||||
t.val24 = index;
|
||||
|
||||
assert((index >= 0 && t.remains == 0) ||
|
||||
(index < 0 && t.remains == 255));
|
||||
|
||||
return t.val32;
|
||||
}
|
||||
|
||||
void decodePtr(int ptr, out PT type, out int index)
|
||||
{
|
||||
_CodePtr t;
|
||||
t.val32 = ptr;
|
||||
|
||||
// Manage negative numbers
|
||||
if(t.val24 >= 0x800000)
|
||||
{
|
||||
t.remains = 255;
|
||||
assert(t.val24 < 0);
|
||||
}
|
||||
|
||||
type = cast(PT) t.type;
|
||||
index = t.val24;
|
||||
|
||||
assert(type != 0 || index == 0,
|
||||
"null pointers must have index == 0");
|
||||
}
|
||||
|
||||
// Getting the name of an enum should be much easier than creating
|
||||
// braindead constructions like this. Although this is still much
|
||||
// better than the C++ equivalent. I'm just happy I did it through a
|
||||
// script instead of typing it all by hand.
|
||||
|
||||
// These kind of braindead constructions will luckily be completely
|
||||
// unnecessary in Monster script, We will not only will have .name
|
||||
// property on enums, but make it easy to assign other values (like
|
||||
// numbers and descriptions) to them as well.
|
||||
char[][] errorToString =
|
||||
[
|
||||
Err.None: "No error!",
|
||||
Err.NoReturn: "Function ended without returning a value"
|
||||
];
|
||||
|
||||
char[][] bcToString =
|
||||
[
|
||||
BC.Exit: "Exit",
|
||||
BC.Call: "Call",
|
||||
BC.CallFar: "CallFar",
|
||||
BC.Return: "Return",
|
||||
BC.ReturnVal: "ReturnVal",
|
||||
BC.ReturnValN: "ReturnValN",
|
||||
BC.State: "State",
|
||||
BC.EnumValue: "EnumValue",
|
||||
BC.EnumField: "EnumField",
|
||||
BC.EnumValToIndex: "EnumValToIndex",
|
||||
BC.EnumNameToIndex: "EnumNameToIndex",
|
||||
BC.New: "New",
|
||||
BC.Jump: "Jump",
|
||||
BC.JumpZ: "JumpZ",
|
||||
BC.JumpNZ: "JumpNZ",
|
||||
BC.PushData: "PushData",
|
||||
BC.PushLocal: "PushLocal",
|
||||
BC.PushClassVar: "PushClassVar",
|
||||
BC.PushParentVar: "PushParentVar",
|
||||
BC.PushFarClassVar: "PushFarClassVar",
|
||||
BC.PushFarClassMulti: "PushFarClassMulti",
|
||||
BC.PushThis: "PushThis",
|
||||
BC.PushSingleton: "PushSingleton",
|
||||
BC.Push8: "Push8",
|
||||
BC.PushLocal8: "PushLocal8",
|
||||
BC.PushClassVar8: "PushClassVar8",
|
||||
BC.PushFarClassVar8: "PushFarClassVar8",
|
||||
BC.Pop: "Pop",
|
||||
BC.PopN: "PopN",
|
||||
BC.Dup: "Dup",
|
||||
BC.Store: "Store",
|
||||
BC.Store8: "Store8",
|
||||
BC.StoreMult: "StoreMult",
|
||||
BC.RefFunc: "RefFunc",
|
||||
BC.FetchElem: "FetchElem",
|
||||
BC.GetArrLen: "GetArrLen",
|
||||
BC.IMul: "IMul",
|
||||
BC.IAdd: "IAdd",
|
||||
BC.ISub: "ISub",
|
||||
BC.IDiv: "IDiv",
|
||||
BC.IDivRem: "IDivRem",
|
||||
BC.UDiv: "UDiv",
|
||||
BC.UDivRem: "UDivRem",
|
||||
BC.INeg: "INeg",
|
||||
BC.LMul: "LMul",
|
||||
BC.LAdd: "LAdd",
|
||||
BC.LSub: "LSub",
|
||||
BC.LDiv: "LDiv",
|
||||
BC.LDivRem: "LDivRem",
|
||||
BC.ULDiv: "ULDiv",
|
||||
BC.ULDivRem: "ULDivRem",
|
||||
BC.LNeg: "LNeg",
|
||||
BC.DMul: "DMul",
|
||||
BC.DAdd: "DAdd",
|
||||
BC.DSub: "DSub",
|
||||
BC.DDiv: "DDiv",
|
||||
BC.DIDiv: "DIDiv",
|
||||
BC.DDivRem: "DDivRem",
|
||||
BC.DNeg: "DNeg",
|
||||
BC.FAdd: "FAdd",
|
||||
BC.FSub: "FSub",
|
||||
BC.FMul: "FMul",
|
||||
BC.FDiv: "FDiv",
|
||||
BC.FIDiv: "FIDiv",
|
||||
BC.FDivRem: "FDivRem",
|
||||
BC.FNeg: "FNeg",
|
||||
BC.IsEqual: "IsEqual",
|
||||
BC.IsEqualMulti: "IsEqualMulti",
|
||||
BC.IsCaseEqual: "IsCaseEqual",
|
||||
BC.CmpArray: "CmpArray",
|
||||
BC.ICmpStr: "ICmpStr",
|
||||
BC.PreInc: "PreInc",
|
||||
BC.PreDec: "PreDec",
|
||||
BC.PostInc: "PostInc",
|
||||
BC.PostDec: "PostDec",
|
||||
BC.PreInc8: "PreInc8",
|
||||
BC.PreDec8: "PreDec8",
|
||||
BC.PostInc8: "PostInc8",
|
||||
BC.PostDec8: "PostDec8",
|
||||
BC.Not: "Not",
|
||||
BC.ILess: "ILess",
|
||||
BC.ULess: "ULess",
|
||||
BC.LLess: "LLess",
|
||||
BC.ULLess: "ULLess",
|
||||
BC.FLess: "FLess",
|
||||
BC.DLess: "DLess",
|
||||
BC.CastI2L: "CastI2L",
|
||||
BC.CastI2F: "CastI2F",
|
||||
BC.CastU2F: "CastU2F",
|
||||
BC.CastL2F: "CastL2F",
|
||||
BC.CastUL2F: "CastUL2F",
|
||||
BC.CastD2F: "CastD2F",
|
||||
BC.CastI2D: "CastI2D",
|
||||
BC.CastU2D: "CastU2D",
|
||||
BC.CastL2D: "CastL2D",
|
||||
BC.CastUL2D: "CastUL2D",
|
||||
BC.CastF2D: "CastF2D",
|
||||
BC.CastF2I: "CastF2I",
|
||||
BC.CastF2U: "CastF2U",
|
||||
BC.CastF2L: "CastF2L",
|
||||
BC.CastF2UL: "CastF2UL",
|
||||
BC.CastD2I: "CastD2I",
|
||||
BC.CastD2U: "CastD2U",
|
||||
BC.CastD2L: "CastD2L",
|
||||
BC.CastD2UL: "CastD2UL",
|
||||
BC.CastS2C: "CastS2C",
|
||||
BC.CastT2S: "CastT2S",
|
||||
BC.DownCast: "DownCast",
|
||||
BC.PopToArray: "PopToArray",
|
||||
BC.NewArray: "NewArray",
|
||||
BC.CopyArray: "CopyArray",
|
||||
BC.DupArray: "DupArray",
|
||||
BC.MakeConstArray: "MakeConstArray",
|
||||
BC.IsConstArray: "IsConstArray",
|
||||
BC.Slice: "Slice",
|
||||
BC.FillArray: "FillArray",
|
||||
BC.CatArray: "CatArray",
|
||||
BC.CatLeft: "CatLeft",
|
||||
BC.CatRight: "CatRight",
|
||||
BC.CreateArrayIter: "CreateArrayIter",
|
||||
BC.IterNext: "IterNext",
|
||||
BC.IterBreak: "IterBreak",
|
||||
BC.IterUpdate: "IterUpdate",
|
||||
BC.CreateClassIter: "CreateClassIter",
|
||||
BC.GetStack: "GetStack",
|
||||
BC.MultiByte: "MultiByte",
|
||||
BC.Error: "Error",
|
||||
];
|
@ -1,133 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (enums.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.compiler.enums;
|
||||
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.types;
|
||||
import monster.compiler.expression;
|
||||
import monster.compiler.statement;
|
||||
import monster.compiler.tokenizer;
|
||||
|
||||
import monster.vm.error;
|
||||
|
||||
// Definition of a field
|
||||
struct FieldDef
|
||||
{
|
||||
Token name;
|
||||
Type type;
|
||||
}
|
||||
|
||||
// Definition of each entry in the enum
|
||||
struct EnumEntry
|
||||
{
|
||||
Token name; // Entry identifier
|
||||
int index; // Enum index value
|
||||
char[] stringValue; // Returned when printing the value
|
||||
|
||||
long value; // Numeric value assigned to the enum. Can be set by the
|
||||
// user, but defaults to 0 for the first entry and to
|
||||
// the previous entry+1 when no value is set.
|
||||
|
||||
Expression exp[]; // Field values (before resolving)
|
||||
int[][] fields; // Field values
|
||||
}
|
||||
|
||||
class EnumDeclaration : TypeDeclaration
|
||||
{
|
||||
static bool canParse(TokenArray toks)
|
||||
{ return toks.isNext(TT.Enum); }
|
||||
|
||||
EnumType type;
|
||||
|
||||
override:
|
||||
void parse(ref TokenArray toks)
|
||||
{
|
||||
type = new EnumType();
|
||||
|
||||
reqNext(toks, TT.Enum);
|
||||
reqNext(toks, TT.Identifier, type.nameTok);
|
||||
|
||||
// Field definitions?
|
||||
while(isNext(toks, TT.Colon))
|
||||
{
|
||||
FieldDef fd;
|
||||
fd.type = Type.identify(toks);
|
||||
reqNext(toks, TT.Identifier, fd.name);
|
||||
type.fields ~= fd;
|
||||
}
|
||||
|
||||
reqNext(toks, TT.LeftCurl);
|
||||
|
||||
type.name = type.nameTok.str;
|
||||
type.loc = type.nameTok.loc;
|
||||
|
||||
// Parse the entries and their fields
|
||||
int lastVal = -1;
|
||||
while(!isNext(toks, TT.RightCurl))
|
||||
{
|
||||
EnumEntry entry;
|
||||
reqNext(toks, TT.Identifier, entry.name);
|
||||
|
||||
// Get the given value, if any
|
||||
if(isNext(toks, TT.Equals))
|
||||
{
|
||||
Token num;
|
||||
reqNext(toks, TT.IntLiteral, num);
|
||||
lastVal = LiteralExpr.parseIntLiteral(num);
|
||||
}
|
||||
else
|
||||
// No given value, just increase the last one given
|
||||
lastVal++;
|
||||
|
||||
entry.value = lastVal;
|
||||
|
||||
while(isNext(toks, TT.Colon))
|
||||
entry.exp ~= Expression.identify(toks);
|
||||
|
||||
reqSep(toks, TT.Comma);
|
||||
|
||||
type.entries ~= entry;
|
||||
}
|
||||
|
||||
if(type.entries.length == 0)
|
||||
fail("Enum is empty", type.loc);
|
||||
|
||||
isNext(toks, TT.Semicolon);
|
||||
}
|
||||
|
||||
void insertType(TFVScope last)
|
||||
{
|
||||
// Insert ourselves into the parent scope
|
||||
assert(last !is null);
|
||||
assert(type !is null);
|
||||
last.insertEnum(type);
|
||||
}
|
||||
|
||||
void resolve(Scope last)
|
||||
{
|
||||
// Delegate to the type, since all the variables are defined
|
||||
// there.
|
||||
type.resolve(last);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,74 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (linespec.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
/* This module provides a simple system for converting positions in a
|
||||
code segment (of compiled byte code) into a line number in source
|
||||
code.
|
||||
*/
|
||||
|
||||
module monster.compiler.linespec;
|
||||
|
||||
import monster.vm.error;
|
||||
|
||||
// A line specification. These are usually found in a list, one for
|
||||
// each buffer of code. All instructions after position 'pos' belong
|
||||
// to line 'line', unless a new LineSpec comes after it that covers
|
||||
// that position.
|
||||
struct LineSpec
|
||||
{
|
||||
int pos; // Offset of instruction
|
||||
int line; // Line number for instructions after this offset
|
||||
}
|
||||
|
||||
// Find the line belonging to a given position. This does not need to
|
||||
// be fast, it is only used for error messages and the like.
|
||||
int findLine(LineSpec[] list, int pos)
|
||||
{
|
||||
int lastpos = -1;
|
||||
int lastline = -1;
|
||||
|
||||
assert(pos >= 0);
|
||||
|
||||
// The first entry must represent pos = 0
|
||||
if(list.length && list[0].pos != 0)
|
||||
fail("Invalid line list: first offset not zero");
|
||||
|
||||
foreach(ls; list)
|
||||
{
|
||||
if(ls.pos <= lastpos)
|
||||
fail("Invalid line list: decreasing offset");
|
||||
|
||||
// Have we searched past pos?
|
||||
if(ls.pos > pos)
|
||||
// If so, the last entry was the correct one
|
||||
return lastline;
|
||||
|
||||
lastpos = ls.pos;
|
||||
lastline = ls.line;
|
||||
}
|
||||
|
||||
// We never searched past our position, that means the last entry is
|
||||
// the most correct.
|
||||
return lastline;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,377 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (properties.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.compiler.properties;
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.types;
|
||||
import monster.compiler.assembler;
|
||||
import monster.vm.mclass;
|
||||
|
||||
import std.stdio;
|
||||
|
||||
/* This module contains special scopes for builtin types. These are
|
||||
used to resolve type properties like .length for arrays and .min
|
||||
and .max for ints, etc.
|
||||
*/
|
||||
|
||||
// TODO: This is nice, but could be nicer. I would like most of these
|
||||
// stored as values rather than functions, and have a general
|
||||
// mechanism for converting values into data (a converted version of
|
||||
// pushInit in Type) used for all types except the ones requiring a
|
||||
// function. I guess I could make versions of insert(s) that takes
|
||||
// floats, ints etc as parameters, and double checks it against the
|
||||
// type. The main reason for storing values is that most of them can
|
||||
// then be optimized away as compile time constants. This is not a
|
||||
// priority.
|
||||
|
||||
class NumericProperties(T) : SimplePropertyScope
|
||||
{
|
||||
this()
|
||||
{
|
||||
super(T.stringof ~ "Properties",
|
||||
GenericProperties.singleton);
|
||||
|
||||
// Static properties of all numeric types
|
||||
static if(T.sizeof == 4)
|
||||
{
|
||||
inserts("min", T.stringof, { tasm.push(T.min); });
|
||||
inserts("max", T.stringof, { tasm.push(T.max); });
|
||||
}
|
||||
else static if(T.sizeof == 8)
|
||||
{
|
||||
inserts("min", T.stringof, { tasm.push8(T.min); });
|
||||
inserts("max", T.stringof, { tasm.push8(T.max); });
|
||||
}
|
||||
else static assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
class IntProperties : NumericProperties!(int)
|
||||
{ static IntProperties singleton; }
|
||||
|
||||
class UintProperties : NumericProperties!(uint)
|
||||
{ static UintProperties singleton; }
|
||||
|
||||
class LongProperties : NumericProperties!(long)
|
||||
{ static LongProperties singleton; }
|
||||
|
||||
class UlongProperties : NumericProperties!(ulong)
|
||||
{ static UlongProperties singleton; }
|
||||
|
||||
class FloatingProperties(T) : NumericProperties!(T)
|
||||
{
|
||||
this()
|
||||
{
|
||||
char[] tp = T.stringof;
|
||||
|
||||
// Additional static properties of floating point numbers
|
||||
static if(T.sizeof == 4)
|
||||
{
|
||||
inserts("infinity", tp, { tasm.push(T.infinity); });
|
||||
inserts("inf", tp, { tasm.push(T.infinity); });
|
||||
inserts("nan", tp, { tasm.push(T.nan); });
|
||||
inserts("epsilon", tp, { tasm.push(T.epsilon); });
|
||||
}
|
||||
else static if(T.sizeof == 8)
|
||||
{
|
||||
inserts("infinity", tp, { tasm.push8(T.infinity); });
|
||||
inserts("inf", tp, { tasm.push8(T.infinity); });
|
||||
inserts("nan", tp, { tasm.push8(T.nan); });
|
||||
inserts("epsilon", tp, { tasm.push8(T.epsilon); });
|
||||
}
|
||||
else static assert(0);
|
||||
|
||||
inserts("dig", "int", { tasm.push(T.dig); });
|
||||
inserts("max_10_exp", "int", { tasm.push(T.max_10_exp); });
|
||||
inserts("max_exp", "int", { tasm.push(T.max_exp); });
|
||||
inserts("min_10_exp", "int", { tasm.push(T.min_10_exp); });
|
||||
inserts("min_exp", "int", { tasm.push(T.min_exp); });
|
||||
|
||||
// Number of bits in mantissa. D calls it mant_dig, but
|
||||
// mant_bits is more natural. Let us allow both.
|
||||
inserts("mant_bits", "int", { tasm.push(T.mant_dig); });
|
||||
inserts("mant_dig", "int", { tasm.push(T.mant_dig); });
|
||||
|
||||
// Lets add in number of bits in the exponent as well.
|
||||
inserts("exp_bits", "int", { tasm.push(cast(uint)(8*T.sizeof-T.mant_dig)); });
|
||||
}
|
||||
}
|
||||
|
||||
class FloatProperties : FloatingProperties!(float)
|
||||
{ static FloatProperties singleton; }
|
||||
|
||||
class DoubleProperties : FloatingProperties!(double)
|
||||
{ static DoubleProperties singleton; }
|
||||
|
||||
// Function pointers / references
|
||||
class FuncRefProperties : SimplePropertyScope
|
||||
{
|
||||
static FuncRefProperties singleton;
|
||||
|
||||
this()
|
||||
{
|
||||
super("FuncRefProperties", GenericProperties.singleton);
|
||||
|
||||
// A function ref is object + function index. Just pop away the
|
||||
// function and the object is left on the stack. Since we cannot
|
||||
// possibly know the type of the object at compile time, we
|
||||
// default to 'Object' as the class type.
|
||||
auto ot = MonsterClass.getObject().objType;
|
||||
assert(ot !is null);
|
||||
insert("obj", ot, { tasm.pop(); });
|
||||
|
||||
// TODO: Replace this with something else later (like a Function
|
||||
// class or similar)
|
||||
insert("func", ArrayType.getString(), { tasm.refFunc(); });
|
||||
}
|
||||
}
|
||||
|
||||
// Handles .length, .dup, etc for arrays
|
||||
class ArrayProperties: SimplePropertyScope
|
||||
{
|
||||
static ArrayProperties singleton;
|
||||
|
||||
this()
|
||||
{
|
||||
super("ArrayProperties", GenericProperties.singleton);
|
||||
|
||||
insert("length", "int",
|
||||
{ tasm.getArrayLength(); },
|
||||
{ assert(0, "cannot set length yet"); });
|
||||
insert("dup", "owner", { tasm.dupArray(); });
|
||||
insert("reverse", "owner", { tasm.reverseArray(); });
|
||||
insert("sort", "owner", { assert(0, "sort not implemented"); });
|
||||
insert("const", "owner", { tasm.makeArrayConst(); });
|
||||
insert("isConst", "bool", { tasm.isArrayConst(); });
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Rename to ObjectProperties
|
||||
class ClassProperties : SimplePropertyScope
|
||||
{
|
||||
static ClassProperties singleton;
|
||||
|
||||
this()
|
||||
{
|
||||
super("ClassProperties",
|
||||
GenericProperties.singleton);
|
||||
|
||||
insert("clone", "owner", { tasm.cloneObj(); });
|
||||
|
||||
// We should move handling of states here. This will mean
|
||||
// removing StateStatement and making states a propert type. We
|
||||
// can't leave statestatement in as a special syntax for setting
|
||||
// types, because the member syntax obj.state = state.label;
|
||||
// would still have to be handled somehow. However, even if this
|
||||
// is more work, it has some additional benefits of allowing
|
||||
// states to be used in other expressions, eg state ==
|
||||
// SomeState. And we should still be able to optimize it into
|
||||
// one instruction.
|
||||
|
||||
// One downside now is that we are currently using static
|
||||
// properties. If we are going to use non-static properties and
|
||||
// allow both member and non-member access, we have to
|
||||
// differentiate between near and far properties too. Think more
|
||||
// about it.
|
||||
//insert("state", "int", { tasm.push(6); });
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamically handles properties like init and sizeof that are valid
|
||||
// for all types.
|
||||
class GenericProperties : SimplePropertyScope
|
||||
{
|
||||
static GenericProperties singleton;
|
||||
|
||||
this()
|
||||
{
|
||||
super("GenericProperties");
|
||||
|
||||
inserts("init", "owner", {assert(0);});
|
||||
inserts("sizeof", "int", {assert(0);});
|
||||
inserts("bitsof", "int", {assert(0);});
|
||||
}
|
||||
|
||||
// Overwrite the above actions
|
||||
void getValue(char[] name, Type oType)
|
||||
{
|
||||
if(oType.isMeta) oType = oType.getBase();
|
||||
|
||||
if(name == "sizeof") tasm.push(oType.getSize);
|
||||
else if(name == "bitsof") tasm.push(oType.getSize*32);
|
||||
else if(name == "init") oType.pushInit();
|
||||
else assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* This is a base class that simplifies definition of property
|
||||
scopes. You can simply call insert and inserts (for static
|
||||
properties) in the constructor. An example:
|
||||
|
||||
inserts("max", "int", { tasm.push(int.max); });
|
||||
|
||||
This inserts the property "max", of type "int", and pushes the
|
||||
value int.max whenever it is invoked. Since it is a static
|
||||
property, the left hand side (eg an int value) is never
|
||||
evaluated. If the type is "" or "owner", then the property type
|
||||
will be the same as the owner type.
|
||||
|
||||
It's likely that parts of this will be simplified or rewritten
|
||||
later. One thing that's missing is compile time properties, for
|
||||
example.
|
||||
*/
|
||||
|
||||
abstract class SimplePropertyScope : PropertyScope
|
||||
{
|
||||
this(char[] n, PropertyScope ps = null) { super(n, ps); }
|
||||
|
||||
private SP[char[]] propList;
|
||||
|
||||
// Convert a typename to a type
|
||||
private Type getType(char[] tp)
|
||||
{
|
||||
if(tp == "" || tp == "owner")
|
||||
return null;
|
||||
|
||||
return BasicType.get(tp);
|
||||
}
|
||||
|
||||
// Insert properties into the list
|
||||
void insert(char[] name, Type tp, Action push, Action pop = null)
|
||||
{
|
||||
assert(!hasProperty(name));
|
||||
propList[name] = SP(tp, false, push, pop);
|
||||
}
|
||||
|
||||
void insert(char[] name, char[] tp, Action push, Action pop = null)
|
||||
{ insert(name, getType(tp), push, pop); }
|
||||
|
||||
// Insert static properties. TODO: These should take values rather
|
||||
// than code. It should be possible to retireve this value at
|
||||
// compile-time.
|
||||
void inserts(char[] name, Type tp, Action push)
|
||||
{
|
||||
assert(!hasProperty(name));
|
||||
propList[name] = SP(tp, true, push, null);
|
||||
}
|
||||
|
||||
void inserts(char[] name, char[] tp, Action push)
|
||||
{ inserts(name, getType(tp), push); }
|
||||
|
||||
// TODO: These are hacks to work around a silly but irritating DMD
|
||||
// feature. Whenever there's an error somewhere, function literals
|
||||
// like { something; } get resolved as int delegate() instead of
|
||||
// void delegate() for some reason. This gives a ton of error
|
||||
// messages, but these overloads will prevent that.
|
||||
alias int delegate() FakeIt;
|
||||
void insert(char[],char[],FakeIt,FakeIt pop = null) {assert(0);}
|
||||
void insert(char[],Type,FakeIt,FakeIt pop = null) {assert(0);}
|
||||
void inserts(char[],char[],FakeIt) {assert(0);}
|
||||
void inserts(char[],Type,FakeIt) {assert(0);}
|
||||
|
||||
override:
|
||||
|
||||
// Return the stored type. If it is null, return the owner type
|
||||
// instead.
|
||||
Type getType(char[] name, Type oType)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
Type tp = propList[name].type;
|
||||
|
||||
// No stored type? We have to copy the owner.
|
||||
if(tp is null)
|
||||
{
|
||||
// The owner type might be a meta-type (eg. int.init). Pretend
|
||||
// it is the base type instead.
|
||||
if(oType.isMeta())
|
||||
tp = oType.getBase();
|
||||
else
|
||||
tp = oType;
|
||||
}
|
||||
|
||||
return tp;
|
||||
}
|
||||
|
||||
void getValue(char[] name, Type oType)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
propList[name].push();
|
||||
}
|
||||
|
||||
void setValue(char[] name, Type oType)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
propList[name].pop();
|
||||
}
|
||||
|
||||
bool hasProperty(char[] name)
|
||||
{ return (name in propList) != null; }
|
||||
|
||||
bool isStatic(char[] name, Type oType)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
return propList[name].isStatic;
|
||||
}
|
||||
|
||||
bool isLValue(char[] name, Type oType)
|
||||
{
|
||||
assert(hasProperty(name));
|
||||
return propList[name].isLValue();
|
||||
}
|
||||
}
|
||||
|
||||
alias void delegate() Action;
|
||||
struct SP
|
||||
{
|
||||
Action push, pop;
|
||||
Type type;
|
||||
bool isStatic;
|
||||
|
||||
static SP opCall(Type tp, bool stat, Action push, Action pop)
|
||||
{
|
||||
SP s;
|
||||
s.push = push;
|
||||
s.pop = pop;
|
||||
s.type = tp;
|
||||
s.isStatic = stat;
|
||||
assert(!stat || pop == null);
|
||||
return s;
|
||||
}
|
||||
|
||||
bool isLValue() { return pop != null; }
|
||||
}
|
||||
|
||||
void initProperties()
|
||||
{
|
||||
GenericProperties.singleton = new GenericProperties;
|
||||
ArrayProperties.singleton = new ArrayProperties;
|
||||
IntProperties.singleton = new IntProperties;
|
||||
UintProperties.singleton = new UintProperties;
|
||||
LongProperties.singleton = new LongProperties;
|
||||
UlongProperties.singleton = new UlongProperties;
|
||||
FloatProperties.singleton = new FloatProperties;
|
||||
DoubleProperties.singleton = new DoubleProperties;
|
||||
ClassProperties.singleton = new ClassProperties;
|
||||
FuncRefProperties.singleton = new FuncRefProperties;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,262 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (states.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.compiler.states;
|
||||
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.assembler;
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.linespec;
|
||||
import monster.compiler.statement;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.error;
|
||||
|
||||
import monster.util.aa;
|
||||
|
||||
import std.stdio;
|
||||
|
||||
struct State
|
||||
{
|
||||
MonsterClass owner; // This must be the first entry, since we're
|
||||
// using some pointer trickery with Function and
|
||||
// State.
|
||||
|
||||
LineSpec[] lines; // Line specifications for byte code
|
||||
ubyte[] bcode; // Final compiled code
|
||||
Token name;
|
||||
int index;
|
||||
|
||||
// Labels in this scope.
|
||||
HashTable!(char[], StateLabel*) labels;
|
||||
StateLabel* labelList[];
|
||||
|
||||
// Cache the begin label since it has special meaning and is looked
|
||||
// up often.
|
||||
StateLabel* begin;
|
||||
|
||||
StateScope sc; // Scope for this state
|
||||
|
||||
// State declaration - used to resolve forward references. Should
|
||||
// not be kept around when compilation is finished.
|
||||
StateDeclaration stateDec;
|
||||
|
||||
StateLabel* findLabel(char[] name)
|
||||
{
|
||||
StateLabel *lb;
|
||||
if(labels.inList(name, lb))
|
||||
return lb;
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look up a label in this state, or register a forward reference if
|
||||
// the state hasn't been resolved yet.
|
||||
void registerGoto(char[] label, LabelUser lu)
|
||||
{
|
||||
StateLabel *sl;
|
||||
|
||||
assert(lu !is null);
|
||||
|
||||
if( labels.inList(label, sl) )
|
||||
lu.setLabel(sl.ls);
|
||||
else
|
||||
{
|
||||
if(stateDec is null)
|
||||
{
|
||||
// The state has been resolved, and the label was not
|
||||
// found. Let lu handle the error message.
|
||||
lu.setLabel(null);
|
||||
assert(0);
|
||||
}
|
||||
|
||||
with(stateDec)
|
||||
{
|
||||
// The state is not resolved yet, so create a forward
|
||||
// reference to this label.
|
||||
Forward *fw;
|
||||
|
||||
// Get the pointer to the Forward struct in the AA, or a
|
||||
// new one if none existed.
|
||||
forwards.insertEdit(label, fw);
|
||||
|
||||
// Add the reference to the list
|
||||
fw.lus ~= lu;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct StateLabel
|
||||
{
|
||||
Token name;
|
||||
uint offs;
|
||||
uint index; // Index used to represent this label in byte code
|
||||
LabelStatement ls; // TODO: Remove this later?
|
||||
}
|
||||
|
||||
// Simple struct used for representing a label and its state in one
|
||||
// value.
|
||||
struct StateLabelPair
|
||||
{
|
||||
State *state;
|
||||
StateLabel *label;
|
||||
}
|
||||
|
||||
// Handles declaration of states at the class scope. Uses a code block
|
||||
// for state contents.
|
||||
class StateDeclaration : Statement
|
||||
{
|
||||
State *st;
|
||||
|
||||
CodeBlock code;
|
||||
|
||||
static struct Forward
|
||||
{ LabelUser lus[]; }
|
||||
|
||||
HashTable!(char[], Forward) forwards;
|
||||
|
||||
static bool canParse(TokenArray toks)
|
||||
{
|
||||
return isNext(toks, TT.State);
|
||||
}
|
||||
|
||||
void parse(ref TokenArray toks)
|
||||
{
|
||||
st = new State;
|
||||
st.stateDec = this;
|
||||
|
||||
if(!isNext(toks, TT.State))
|
||||
assert(0, "Internal error in StateDeclaration");
|
||||
|
||||
if(!isNext(toks, TT.Identifier, st.name))
|
||||
fail("Expected state name identifier", toks);
|
||||
|
||||
// Create a code block, and tell it (the parameter) that it is a
|
||||
// state block.
|
||||
code = new CodeBlock(true);
|
||||
code.parse(toks);
|
||||
}
|
||||
|
||||
// Resolve the state. Besides resolving the code we have to resolve
|
||||
// any forward references to labels within the state afterwards.
|
||||
void resolve(Scope last)
|
||||
{
|
||||
assert(st !is null);
|
||||
// Create a state scope. The scope will help enforce special
|
||||
// rules, such as allowing idle functions and disallowing
|
||||
// variable declarations.
|
||||
st.sc = new StateScope(last, st);
|
||||
st.owner = st.sc.getClass();
|
||||
|
||||
// Resolve the interior of the code block
|
||||
assert(code !is null);
|
||||
code.resolve(st.sc);
|
||||
|
||||
// Go through the forward list and resolve everything
|
||||
foreach(char[] label, Forward fd; forwards)
|
||||
{
|
||||
StateLabel *sl;
|
||||
LabelStatement ls;
|
||||
if(st.labels.inList(label, sl))
|
||||
{
|
||||
assert(sl !is null);
|
||||
ls = sl.ls;
|
||||
}
|
||||
else
|
||||
ls = null; // Give a null to setLabel and let it handle
|
||||
// the error message.
|
||||
|
||||
// Loop through the label users
|
||||
foreach(LabelUser lu; fd.lus)
|
||||
lu.setLabel(ls);
|
||||
|
||||
// setLabel should have thrown an error at this point
|
||||
assert(ls !is null);
|
||||
}
|
||||
|
||||
// Clear the forwards list
|
||||
forwards.reset();
|
||||
|
||||
// At this point the State no longer needs to refer to us. Set
|
||||
// the stateDec reference to null. This is also a signal to
|
||||
// State.registerGoto that the state has been resolved, and no
|
||||
// further forward references will be accepted.
|
||||
st.stateDec = null;
|
||||
|
||||
// After the code has been resolved, all labels should now be
|
||||
// registered in the 'labels' list. We must assign a number to
|
||||
// each label for later reference. We also set up the labelList
|
||||
// which can be used to look up the labels directly.
|
||||
int cnt = 0;
|
||||
st.labelList.length = st.labels.length;
|
||||
foreach(char[] name, StateLabel *sl; st.labels)
|
||||
{
|
||||
assert(sl !is null);
|
||||
assert(name == sl.name.str, "label name mismatch");
|
||||
sl.index = cnt++;
|
||||
st.labelList[sl.index] = sl;
|
||||
|
||||
// Cache the 'begin:' label
|
||||
if(name == "begin")
|
||||
{
|
||||
assert(st.begin is null);
|
||||
st.begin = sl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compile it as a function.
|
||||
void compile()
|
||||
{
|
||||
// No forward references must be inserted after the state has
|
||||
// been resolved.
|
||||
assert(forwards.length == 0);
|
||||
|
||||
tasm.newFunc();
|
||||
code.compile();
|
||||
|
||||
// Table used to fetch the offset for the labels in this state.
|
||||
uint offsets[];
|
||||
|
||||
offsets.length = st.labels.length;
|
||||
|
||||
// Assemble the code and get the offsets
|
||||
st.bcode = tasm.assemble(st.lines, offsets);
|
||||
|
||||
// Store the offsets in the label statements themselves, for
|
||||
// later use
|
||||
int cnt = 0;
|
||||
foreach(StateLabel* ls; st.labels)
|
||||
{
|
||||
ls.offs = offsets[ls.index];
|
||||
}
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
{
|
||||
return
|
||||
"State declaration: " ~
|
||||
st.name.str ~ "\n" ~
|
||||
code.toString();
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (structs.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.compiler.structs;
|
||||
|
||||
import monster.compiler.types;
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.variables;
|
||||
import monster.compiler.functions;
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.statement;
|
||||
import monster.vm.error;
|
||||
import std.stdio;
|
||||
|
||||
class StructDeclaration : TypeDeclaration
|
||||
{
|
||||
StructType type;
|
||||
Token name;
|
||||
|
||||
private:
|
||||
FuncDeclaration[] funcdecs;
|
||||
VarDeclStatement[] vardecs;
|
||||
|
||||
// Identify what kind of block the given set of tokens represent,
|
||||
// parse them, and store it in the appropriate list;
|
||||
void store(ref TokenArray toks)
|
||||
{
|
||||
// canParse() is not ment as a complete syntax test, only to be
|
||||
// enough to identify which Block parser to apply.
|
||||
if(FuncDeclaration.canParse(toks))
|
||||
{
|
||||
auto fd = new FuncDeclaration;
|
||||
funcdecs ~= fd;
|
||||
fd.parse(toks);
|
||||
}
|
||||
else if(VarDeclStatement.canParse(toks))
|
||||
{
|
||||
auto vd = new VarDeclStatement;
|
||||
vd.parse(toks);
|
||||
vardecs ~= vd;
|
||||
}
|
||||
else
|
||||
fail("Illegal type or declaration", toks);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
static bool canParse(TokenArray toks)
|
||||
{ return toks.isNext(TT.Struct); }
|
||||
|
||||
override:
|
||||
void parse(ref TokenArray toks)
|
||||
{
|
||||
if(!isNext(toks, TT.Struct, loc))
|
||||
fail("Internal error in StructDeclaration");
|
||||
|
||||
if(!isNext(toks, TT.Identifier, name))
|
||||
fail("Expected struct name", toks);
|
||||
|
||||
if(!isNext(toks, TT.LeftCurl))
|
||||
fail("Struct expected {", toks);
|
||||
|
||||
// Parse the rest of the file
|
||||
while(!isNext(toks, TT.RightCurl))
|
||||
store(toks);
|
||||
|
||||
// Allow an optional semicolon
|
||||
isNext(toks, TT.Semicolon);
|
||||
}
|
||||
|
||||
void insertType(TFVScope last)
|
||||
{
|
||||
// Set up the struct type.
|
||||
type = new StructType(this);
|
||||
|
||||
// Create a new scope
|
||||
type.sc = new StructScope(last, type);
|
||||
|
||||
// Insert ourselves into the parent scope
|
||||
assert(last !is null);
|
||||
last.insertStruct(this);
|
||||
}
|
||||
|
||||
void resolve(Scope last)
|
||||
{
|
||||
if(type.set)
|
||||
return;
|
||||
|
||||
// Get the total number of variables.
|
||||
uint tot = 0;
|
||||
foreach(dec; vardecs)
|
||||
tot += dec.vars.length;
|
||||
|
||||
// Must have at least one variable per declaration statement
|
||||
assert(tot >= vardecs.length);
|
||||
|
||||
// Get one array containing all the variables
|
||||
Variable*[] vars = new Variable*[tot];
|
||||
int ind = 0;
|
||||
foreach(st; vardecs)
|
||||
foreach(dec; st.vars)
|
||||
vars[ind++] = dec.var;
|
||||
assert(ind == tot);
|
||||
|
||||
// Store the variables in the StructType
|
||||
type.vars = vars;
|
||||
|
||||
// Mark the size as "set" now.
|
||||
type.set = true;
|
||||
|
||||
// Resolve
|
||||
foreach(dec; vardecs)
|
||||
dec.resolve(type.sc);
|
||||
|
||||
// Calculate the struct size
|
||||
type.size = 0;
|
||||
foreach(t; vars)
|
||||
type.size += t.type.getSize();
|
||||
|
||||
// Set up the init value
|
||||
ind = 0;
|
||||
int[] init = new int[type.getSize()];
|
||||
foreach(st; vardecs)
|
||||
foreach(dec; st.vars)
|
||||
{
|
||||
int si = dec.var.type.getSize();
|
||||
init[ind..ind+si] = dec.getCTimeValue();
|
||||
ind += si;
|
||||
}
|
||||
assert(ind == init.length);
|
||||
type.defInit = init;
|
||||
|
||||
// Functions:
|
||||
|
||||
// Disallow anything but normal functions (we can fix static and
|
||||
// native struct functions later.)
|
||||
|
||||
// Struct resolve only resolves header information, it doesn't
|
||||
// resolve function bodies.
|
||||
assert(funcdecs.length == 0, "struct functions not supported yet");
|
||||
/*
|
||||
foreach(dec; funcdecs)
|
||||
type.sc.insertFunc(dec);
|
||||
*/
|
||||
}
|
||||
}
|
@ -1,942 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (tokenizer.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.compiler.tokenizer;
|
||||
|
||||
import std.string;
|
||||
import std.stream;
|
||||
import std.stdio;
|
||||
import std.utf;
|
||||
|
||||
import monster.util.string : begins;
|
||||
|
||||
import monster.vm.error;
|
||||
import monster.options;
|
||||
|
||||
alias Token[] TokenArray;
|
||||
|
||||
// Check if a character is alpha-numerical or an underscore
|
||||
bool validIdentChar(char c)
|
||||
{
|
||||
if(validFirstIdentChar(c) || numericalChar(c))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Same as above, except numbers are not allowed as the first
|
||||
// character. Will extend to support full Unicode later.
|
||||
bool validFirstIdentChar(char c)
|
||||
{
|
||||
if((c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c == '_') ) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isValidIdent(char[] iname)
|
||||
{
|
||||
if(iname.length == 0)
|
||||
return false;
|
||||
|
||||
if(!validFirstIdentChar(iname[0]))
|
||||
return false;
|
||||
|
||||
foreach(char c; iname)
|
||||
if(!validIdentChar(c)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool numericalChar(char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
enum TT
|
||||
{
|
||||
// Syntax characters
|
||||
Semicolon, DDDot, DDot,
|
||||
LeftParen, RightParen,
|
||||
LeftCurl, RightCurl,
|
||||
LeftSquare, RightSquare,
|
||||
Dot, Comma, Colon,
|
||||
|
||||
// Array length symbol
|
||||
Dollar,
|
||||
|
||||
// 'at' sign, @
|
||||
Alpha,
|
||||
|
||||
// Conditional expressions
|
||||
IsEqual, NotEqual,
|
||||
IsCaseEqual, IsCaseEqual2,
|
||||
NotCaseEqual, NotCaseEqual2,
|
||||
Less, More,
|
||||
LessEq, MoreEq,
|
||||
And, Or, Not,
|
||||
|
||||
// Assignment operators.
|
||||
Equals, PlusEq, MinusEq, MultEq, DivEq, RemEq, IDivEq,
|
||||
CatEq,
|
||||
|
||||
// Pre- and postfix increment and decrement operators ++ --
|
||||
PlusPlus, MinusMinus,
|
||||
|
||||
// Arithmetic operators
|
||||
Plus, Minus, Mult, Div, Rem, IDiv,
|
||||
Cat,
|
||||
|
||||
// Keywords. Note that we use Class as a separator below, so it
|
||||
// must be first in this list. All operator tokens must occur
|
||||
// before Class, and all keywords must come after Class.
|
||||
Class, Module, Singleton,
|
||||
If, Else,
|
||||
For, Foreach, ForeachRev,
|
||||
Do, While, Until,
|
||||
Continue, Break,
|
||||
Typeof,
|
||||
Return,
|
||||
Switch, Select,
|
||||
State,
|
||||
Struct, Enum,
|
||||
Import, Clone, Override, Final, Function, With,
|
||||
This, New, Static, Const, Out, Ref, Abstract, Idle,
|
||||
Public, Private, Protected, True, False, Native, Null,
|
||||
Goto, Var,
|
||||
|
||||
Last, // Tokens after this do not have a specific string
|
||||
// associated with them.
|
||||
|
||||
StringLiteral, // "something" or 'something'
|
||||
IntLiteral, // Anything that starts with a number, except
|
||||
// floats
|
||||
FloatLiteral, // Any number which contains a period symbol
|
||||
Identifier, // user-named identifier
|
||||
EOF, // end of file
|
||||
EMPTY // empty line (not stored)
|
||||
}
|
||||
|
||||
struct Token
|
||||
{
|
||||
TT type;
|
||||
char[] str;
|
||||
Floc loc;
|
||||
|
||||
// Translated string literal (with resolved escape codes.)
|
||||
dchar[] str32;
|
||||
|
||||
// True if this token was the first on its line.
|
||||
bool newline;
|
||||
|
||||
char[] toString() { return str; }
|
||||
|
||||
static Token opCall(char[] name, Floc loc)
|
||||
{ return Token(TT.Identifier, name, loc); }
|
||||
static Token opCall(TT tt, char[] name, Floc loc)
|
||||
{
|
||||
Token t;
|
||||
t.type = tt;
|
||||
t.str = name;
|
||||
t.loc = loc;
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
// Used to look up keywords.
|
||||
TT keywordLookup[char[]];
|
||||
bool lookupSetup = false;
|
||||
|
||||
void initTokenizer()
|
||||
{
|
||||
assert(!lookupSetup);
|
||||
|
||||
// Insert the keywords into the lookup table
|
||||
for(TT t = TT.Class; t < TT.Last; t++)
|
||||
{
|
||||
char[] tok = tokenList[t];
|
||||
assert(tok != "");
|
||||
assert((tok in keywordLookup) == null);
|
||||
keywordLookup[tok] = t;
|
||||
}
|
||||
|
||||
lookupSetup = true;
|
||||
}
|
||||
|
||||
// Index table of all the tokens
|
||||
const char[][] tokenList =
|
||||
[
|
||||
TT.Semicolon : ";",
|
||||
TT.DDDot : "...",
|
||||
TT.DDot : "..",
|
||||
TT.LeftParen : "(",
|
||||
TT.RightParen : ")",
|
||||
TT.LeftCurl : "{",
|
||||
TT.RightCurl : "}",
|
||||
TT.LeftSquare : "[",
|
||||
TT.RightSquare : "]",
|
||||
TT.Dot : ".",
|
||||
TT.Comma : ",",
|
||||
TT.Colon : ":",
|
||||
|
||||
TT.Dollar : "$",
|
||||
|
||||
TT.Alpha : "@",
|
||||
|
||||
TT.IsEqual : "==",
|
||||
TT.NotEqual : "!=",
|
||||
|
||||
TT.IsCaseEqual : "=i=",
|
||||
TT.IsCaseEqual2 : "=I=",
|
||||
TT.NotCaseEqual : "!=i=",
|
||||
TT.NotCaseEqual2 : "!=I=",
|
||||
|
||||
TT.Less : "<",
|
||||
TT.More : ">",
|
||||
TT.LessEq : "<=",
|
||||
TT.MoreEq : ">=",
|
||||
TT.And : "&&",
|
||||
TT.Or : "||",
|
||||
TT.Not : "!",
|
||||
|
||||
TT.Equals : "=",
|
||||
TT.PlusEq : "+=",
|
||||
TT.MinusEq : "-=",
|
||||
TT.MultEq : "*=",
|
||||
TT.DivEq : "/=",
|
||||
TT.RemEq : "%=",
|
||||
TT.IDivEq : "\\=",
|
||||
TT.CatEq : "~=",
|
||||
|
||||
TT.PlusPlus : "++",
|
||||
TT.MinusMinus : "--",
|
||||
TT.Cat : "~",
|
||||
|
||||
TT.Plus : "+",
|
||||
TT.Minus : "-",
|
||||
TT.Mult : "*",
|
||||
TT.Div : "/",
|
||||
TT.Rem : "%",
|
||||
TT.IDiv : "\\",
|
||||
|
||||
TT.Class : "class",
|
||||
TT.Module : "module",
|
||||
TT.Return : "return",
|
||||
TT.For : "for",
|
||||
TT.This : "this",
|
||||
TT.New : "new",
|
||||
TT.If : "if",
|
||||
TT.Else : "else",
|
||||
TT.Foreach : "foreach",
|
||||
TT.ForeachRev : "foreach_reverse",
|
||||
TT.Do : "do",
|
||||
TT.While : "while",
|
||||
TT.Until : "until",
|
||||
TT.Continue : "continue",
|
||||
TT.Break : "break",
|
||||
TT.Switch : "switch",
|
||||
TT.Select : "select",
|
||||
TT.State : "state",
|
||||
TT.Struct : "struct",
|
||||
TT.Enum : "enum",
|
||||
TT.Import : "import",
|
||||
TT.Typeof : "typeof",
|
||||
TT.Singleton : "singleton",
|
||||
TT.Clone : "clone",
|
||||
TT.Static : "static",
|
||||
TT.Const : "const",
|
||||
TT.Abstract : "abstract",
|
||||
TT.Override : "override",
|
||||
TT.Final : "final",
|
||||
TT.Function : "function",
|
||||
TT.With : "with",
|
||||
TT.Idle : "idle",
|
||||
TT.Out : "out",
|
||||
TT.Ref : "ref",
|
||||
TT.Public : "public",
|
||||
TT.Private : "private",
|
||||
TT.Protected : "protected",
|
||||
TT.True : "true",
|
||||
TT.False : "false",
|
||||
TT.Native : "native",
|
||||
TT.Null : "null",
|
||||
TT.Goto : "goto",
|
||||
TT.Var : "var",
|
||||
|
||||
// These are only used in error messages
|
||||
TT.StringLiteral : "string literal",
|
||||
TT.IntLiteral : "integer literal",
|
||||
TT.FloatLiteral : "floating point literal",
|
||||
TT.Identifier : "identifier",
|
||||
TT.EOF : "end of file",
|
||||
TT.EMPTY : "empty line - you should never see this"
|
||||
];
|
||||
|
||||
class Tokenizer
|
||||
{
|
||||
private:
|
||||
// Line buffer. Don't worry, this is perfectly safe. It is used by
|
||||
// Stream.readLine, which uses the buffer if it fits and creates a
|
||||
// new one if it doesn't. It is only here to optimize memory usage
|
||||
// (avoid creating a new buffer for each line), and lines longer
|
||||
// than 300 characters will work without problems.
|
||||
char[300] buffer;
|
||||
char[] line; // The rest of the current line
|
||||
Stream inf;
|
||||
int lineNum=-1;
|
||||
char[] fname;
|
||||
bool newline;
|
||||
|
||||
// Make a token of given type with given string, and remove it from
|
||||
// the input line.
|
||||
Token retToken(TT type, char[] str)
|
||||
{
|
||||
Token t;
|
||||
t.type = type;
|
||||
t.str = str;
|
||||
t.newline = newline;
|
||||
t.loc.fname = fname;
|
||||
t.loc.line = lineNum;
|
||||
|
||||
// Special case for =I= and !=I=. Treat them the same as =i= and
|
||||
// !=i=.
|
||||
if(type == TT.IsCaseEqual2) t.type = TT.IsCaseEqual;
|
||||
if(type == TT.NotCaseEqual2) t.type = TT.NotCaseEqual;
|
||||
|
||||
// Treat } as a separator
|
||||
if(type == TT.RightCurl) t.newline = true;
|
||||
|
||||
// Remove the string from 'line', along with any following witespace
|
||||
remWord(str);
|
||||
return t;
|
||||
}
|
||||
|
||||
// Removes 'str' from the beginning of 'line', or from
|
||||
// line[leadIn..$] if leadIn != 0.
|
||||
void remWord(char[] str, int leadIn = 0)
|
||||
{
|
||||
assert(line.length >= leadIn);
|
||||
line = line[leadIn..$];
|
||||
|
||||
assert(line.begins(str));
|
||||
line = line[str.length..$].stripl();
|
||||
}
|
||||
|
||||
Token eofToken()
|
||||
{
|
||||
Token t;
|
||||
t.str = "<end of file>";
|
||||
t.type = TT.EOF;
|
||||
t.newline = true;
|
||||
t.loc.line = lineNum;
|
||||
t.loc.fname = fname;
|
||||
return t;
|
||||
}
|
||||
|
||||
Token empty;
|
||||
|
||||
public:
|
||||
final:
|
||||
// Used when reading tokens from a file or a stream
|
||||
this(char[] fname, Stream inf, int bom)
|
||||
{
|
||||
assert(inf !is null);
|
||||
|
||||
// The BOM (byte order mark) defines the byte order (little
|
||||
// endian or big endian) and the encoding (utf8, utf16 or
|
||||
// utf32).
|
||||
switch(bom)
|
||||
{
|
||||
case -1:
|
||||
// Files without a BOM are interpreted as UTF8
|
||||
case BOM.UTF8:
|
||||
// UTF8 is the default
|
||||
break;
|
||||
|
||||
case BOM.UTF16LE:
|
||||
case BOM.UTF16BE:
|
||||
case BOM.UTF32LE:
|
||||
case BOM.UTF32BE:
|
||||
fail("UTF16 and UTF32 files are not supported yet");
|
||||
default:
|
||||
fail("Unknown BOM value!");
|
||||
}
|
||||
|
||||
this.inf = inf;
|
||||
this.fname = fname;
|
||||
|
||||
this();
|
||||
}
|
||||
|
||||
// This is used for single-line mode, such as in a console.
|
||||
this()
|
||||
{
|
||||
empty.type = TT.EMPTY;
|
||||
}
|
||||
|
||||
void setLine(char[] ln)
|
||||
{
|
||||
assert(inf is null, "setLine only supported in line mode");
|
||||
line = ln;
|
||||
}
|
||||
|
||||
~this() { if(inf !is null) delete inf; }
|
||||
|
||||
void fail(char[] msg)
|
||||
{
|
||||
if(inf !is null)
|
||||
// File mode
|
||||
throw new MonsterException(format("%s:%s: %s", fname, lineNum, msg));
|
||||
else
|
||||
// Line mode
|
||||
throw new MonsterException(msg);
|
||||
}
|
||||
|
||||
// Various parsing modes
|
||||
enum
|
||||
{
|
||||
Normal, // Normal mode
|
||||
Block, // Block comment
|
||||
Nest // Nested block comment
|
||||
}
|
||||
int mode = Normal;
|
||||
int nests = 0; // Nest level
|
||||
|
||||
// Get the next token from the line, if any
|
||||
Token getNextFromLine()
|
||||
{
|
||||
assert(lookupSetup,
|
||||
"Internal error: The tokenizer lookup table has not been set up!");
|
||||
|
||||
restart:
|
||||
|
||||
if(mode == Block)
|
||||
{
|
||||
int index = line.find("*/");
|
||||
|
||||
// If we find a '*/', the comment is done
|
||||
if(index != -1)
|
||||
{
|
||||
mode = Normal;
|
||||
|
||||
// Cut the comment from the input
|
||||
remWord("*/", index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Comment was not terminated on this line, try the next
|
||||
line = null;
|
||||
}
|
||||
}
|
||||
else if(mode == Nest)
|
||||
{
|
||||
// Check for nested /+ and +/ in here, but go to restart if
|
||||
// none is found (meaning the comment continues on the next
|
||||
// line), or reset mode and go to restart if nest level ever
|
||||
// gets to 0.
|
||||
|
||||
while(line.length >= 2)
|
||||
{
|
||||
int incInd = -1;
|
||||
int decInd = -1;
|
||||
// Find the first matching '/+' or '+/
|
||||
foreach(int i, char c; line[0..$-1])
|
||||
{
|
||||
if(c == '/' && line[i+1] == '+')
|
||||
{
|
||||
incInd = i;
|
||||
break;
|
||||
}
|
||||
else if(c == '+' && line[i+1] == '/')
|
||||
{
|
||||
decInd = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a nest level when '/+' is found
|
||||
if(incInd != -1)
|
||||
{
|
||||
remWord("/+", incInd);
|
||||
nests++;
|
||||
continue; // Search more in this line
|
||||
}
|
||||
|
||||
// Remove a nest level when '+/' is found
|
||||
else if(decInd != -1)
|
||||
{
|
||||
// Remove the +/ from input
|
||||
remWord("+/", decInd);
|
||||
|
||||
nests--; // Remove a level
|
||||
assert(nests >= 0);
|
||||
|
||||
// Are we done? If so, return to normal mode.
|
||||
if(nests == 0)
|
||||
{
|
||||
mode = Normal;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nothing found on this line, try the next
|
||||
break;
|
||||
}
|
||||
|
||||
// If we're still in nested comment mode, ignore the rest of
|
||||
// the line
|
||||
if(mode == Nest)
|
||||
line = null;
|
||||
}
|
||||
|
||||
// Comment - ignore the rest of the line
|
||||
if(line.begins("//"))
|
||||
line = null;
|
||||
|
||||
// If the line is empty at this point, there's nothing more to
|
||||
// be done
|
||||
if(line == "")
|
||||
return empty;
|
||||
|
||||
// Block comment
|
||||
if(line.begins("/*"))
|
||||
{
|
||||
mode = Block;
|
||||
line = line[2..$];
|
||||
goto restart;
|
||||
}
|
||||
|
||||
// Nested comment
|
||||
if(line.begins("/+"))
|
||||
{
|
||||
mode = Nest;
|
||||
line = line[2..$];
|
||||
nests++;
|
||||
goto restart;
|
||||
}
|
||||
|
||||
if(line.begins("*/")) fail("Unexpected end of block comment");
|
||||
if(line.begins("+/")) fail("Unexpected end of nested comment");
|
||||
|
||||
// String literals (multi-line literals not implemented yet)
|
||||
if(line.begins("\"") || // Standard string: "abc"
|
||||
line.begins("r\"") || // Wysiwig string: r"c:\dir"
|
||||
line.begins("\\\"") || // ditto: \"c:\dir"
|
||||
line.begins("'") ||
|
||||
line.begins("r'") || // Equivalent ' versions
|
||||
line.begins("\\'"))
|
||||
{
|
||||
bool found = false;
|
||||
bool wysiwig = false;
|
||||
|
||||
// Quote character that terminates this string.
|
||||
char quote;
|
||||
|
||||
char[] slice = line;
|
||||
|
||||
// Removes the first num chars from the line
|
||||
void skip(int num)
|
||||
{
|
||||
assert(num <= line.length);
|
||||
slice = slice[num..$];
|
||||
}
|
||||
|
||||
// Parse the first quotation
|
||||
if(slice[0] == '"' || slice[0] == '\'')
|
||||
{
|
||||
quote = slice[0];
|
||||
skip(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check for wysiwig strings
|
||||
if(slice[0] == '\\' || slice[0] == 'r')
|
||||
wysiwig = true;
|
||||
else assert(0);
|
||||
|
||||
quote = slice[1];
|
||||
skip(2);
|
||||
}
|
||||
|
||||
assert(quote == '"' || quote == '\'');
|
||||
|
||||
// This will store the result
|
||||
dchar[] result;
|
||||
|
||||
// Stores a single character in the result string, and
|
||||
// removes a given number of input characters.
|
||||
void store(dchar ch, int slen)
|
||||
{
|
||||
result ~= ch;
|
||||
skip(slen);
|
||||
}
|
||||
|
||||
// Convert a given code into 'ch', if it is found.
|
||||
void convert(char[] code, dchar ch)
|
||||
{
|
||||
if(slice.begins(code))
|
||||
store(ch, code.length);
|
||||
}
|
||||
|
||||
// Convert given escape character to 'res'
|
||||
void escape(char ch, dchar res)
|
||||
{
|
||||
if(slice.length >= 2 &&
|
||||
slice[0] == '\\' &&
|
||||
slice[1] == ch)
|
||||
store(res, 2);
|
||||
}
|
||||
|
||||
// Interpret string
|
||||
while(slice.length)
|
||||
{
|
||||
int startLen = slice.length;
|
||||
|
||||
// Convert "" to " (or '' to ' in single-quote strings)
|
||||
convert(""~quote~quote, quote);
|
||||
|
||||
// Interpret backslash escape codes if we're not in
|
||||
// wysiwig mode
|
||||
if(!wysiwig)
|
||||
{
|
||||
escape('"', '"'); // \" == literal "
|
||||
escape('\'', '\''); // \' == literal '
|
||||
escape('\\', '\\'); // \\ == literal \
|
||||
|
||||
escape('a', 7); // \a == bell
|
||||
escape('b', 8); // \b == backspace
|
||||
escape('f', 12); // \f == feed form
|
||||
escape('n', '\n'); // \n == newline
|
||||
escape('r', '\r'); // \r == carriage return
|
||||
escape('t', '\t'); // \t == tab
|
||||
escape('v', '\v'); // \v == vertical tab
|
||||
escape('e', 27); // \e == ANSI escape
|
||||
|
||||
// Check for numerical escapes
|
||||
|
||||
// If either of these aren't met, this isn't a valid
|
||||
// escape code.
|
||||
if(slice.length < 2 ||
|
||||
slice[0] != '\\')
|
||||
goto nocode;
|
||||
|
||||
// Checks and converts the digits in slice[] into a
|
||||
// character.
|
||||
void convertNumber(int skp, int maxLen, int base,
|
||||
char[] pattern, char[] name)
|
||||
{
|
||||
assert(base <= 16);
|
||||
|
||||
// Skip backslash and other leading characters
|
||||
skip(skp);
|
||||
|
||||
int len; // Number of digits found
|
||||
uint result = 0;
|
||||
|
||||
for(len=0; len<maxLen; len++)
|
||||
{
|
||||
if(slice.length <= len) break;
|
||||
|
||||
char digit = slice[len];
|
||||
|
||||
// Does the digit qualify?
|
||||
if(!inPattern(digit, pattern))
|
||||
break;
|
||||
|
||||
// Multiply up the existing number to
|
||||
// make room for the digit.
|
||||
result *= base;
|
||||
|
||||
// Convert single digit to a number
|
||||
if(digit >= '0' && digit <= '9')
|
||||
digit -= '0';
|
||||
else if(digit >= 'a' && digit <= 'z')
|
||||
digit -= 'a' - 10;
|
||||
else if(digit >= 'A' && digit <= 'Z')
|
||||
digit -= 'A' - 10;
|
||||
assert(digit >= 0 && digit < base);
|
||||
|
||||
// Add inn the digit
|
||||
result += digit;
|
||||
}
|
||||
|
||||
if(len > 0)
|
||||
{
|
||||
// We got something. Convert it and store
|
||||
// it.
|
||||
store(result, len);
|
||||
}
|
||||
else
|
||||
fail("Invalid " ~ name ~ " escape code");
|
||||
}
|
||||
|
||||
const Dec = "0-9";
|
||||
const Oct = "0-7";
|
||||
const Hex = "0-9a-fA-F";
|
||||
|
||||
// Octal escapes: \0N, \0NN or \0NNN where N are
|
||||
// octal digits (0-7). Also accepts \o instead of
|
||||
// \0.
|
||||
if(slice[1] == '0' || slice[1] == 'o')
|
||||
convertNumber(2, 3, 8, Oct, "octal");
|
||||
|
||||
// Decimal escapes: \N \NN and \NNN, where N are
|
||||
// digits and the first digit is not zero.
|
||||
else if(inPattern(slice[1], Dec))
|
||||
convertNumber(1, 3, 10, Dec, "decimal");
|
||||
|
||||
// Hex escape codes: \xXX where X are hex digits
|
||||
else if(slice[1] == 'x')
|
||||
convertNumber(2, 2, 16, Hex, "hex");
|
||||
|
||||
// Unicode escape codes:
|
||||
// \uXXXX
|
||||
else if(slice[1] == 'u')
|
||||
convertNumber(2, 4, 16, Hex, "Unicode hex");
|
||||
|
||||
// \UXXXXXXXX
|
||||
else if(slice[1] == 'U')
|
||||
convertNumber(2, 8, 16, Hex, "Unicode hex");
|
||||
|
||||
}
|
||||
nocode:
|
||||
|
||||
// If something was converted this round, start again
|
||||
// from the top.
|
||||
if(startLen != slice.length)
|
||||
continue;
|
||||
|
||||
assert(slice.length > 0);
|
||||
|
||||
// Nothing was done. Are we at the end of the string?
|
||||
if(slice[0] == quote)
|
||||
{
|
||||
skip(1);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Unhandled escape code?
|
||||
if(slice[0] == '\\' && !wysiwig)
|
||||
{
|
||||
if(slice.length == 0)
|
||||
// Just a single \ at the end of the line
|
||||
fail("Multiline string literals not implemented");
|
||||
else
|
||||
fail("Unhandled escape code: \\" ~ slice[1]);
|
||||
}
|
||||
|
||||
// Nope. It's just a normal character. Decode it from
|
||||
// UTF8.
|
||||
size_t clen = 0;
|
||||
dchar cres;
|
||||
cres = decode(slice,clen);
|
||||
store(cres, clen);
|
||||
}
|
||||
if(!found) fail("Unterminated string literal '" ~line~ "'");
|
||||
|
||||
// Set 'slice' to contain the original string
|
||||
slice = line[0..(line.length-slice.length)];
|
||||
|
||||
// Set up the token
|
||||
auto t = retToken(TT.StringLiteral, slice.dup);
|
||||
t.str32 = result;
|
||||
return t;
|
||||
}
|
||||
|
||||
// Numerical literals - if it starts with a number, we accept
|
||||
// it, until it is interupted by an unacceptable character. We
|
||||
// also accept numbers on the form .NUM. We do not try to parse
|
||||
// the number here.
|
||||
if(numericalChar(line[0]) ||
|
||||
// Cover the .num case
|
||||
( line.length >= 2 && line[0] == '.' &&
|
||||
numericalChar(line[1]) ))
|
||||
{
|
||||
// Treat the rest as we would an identifier - the actual
|
||||
// interpretation will be done later. We allow non-numerical
|
||||
// tokens in the literal, such as 0x0a or 1_000_000. We must
|
||||
// also explicitly allow '.' dots.
|
||||
int len = 1;
|
||||
bool lastDot = false; // Was the last char a '.'?
|
||||
int dots; // Number of dots
|
||||
foreach(char ch; line[1..$])
|
||||
{
|
||||
if(ch == '.')
|
||||
{
|
||||
// We accept "." but not "..", as this might be an
|
||||
// operator.
|
||||
if(lastDot)
|
||||
{
|
||||
// Remove the last dot and exit.
|
||||
len--;
|
||||
dots--;
|
||||
break;
|
||||
}
|
||||
lastDot = true;
|
||||
dots++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!validIdentChar(ch)) break;
|
||||
lastDot = false;
|
||||
//lastPer = false;
|
||||
}
|
||||
|
||||
// This was a valid character, count it
|
||||
len++;
|
||||
}
|
||||
if(dots != 0)
|
||||
return retToken(TT.FloatLiteral, line[0..len].dup);
|
||||
else
|
||||
return retToken(TT.IntLiteral, line[0..len].dup);
|
||||
}
|
||||
|
||||
// Check for identifiers
|
||||
if(validFirstIdentChar(line[0]))
|
||||
{
|
||||
// It's an identifier or name, find the length
|
||||
int len = 1;
|
||||
foreach(char ch; line[1..$])
|
||||
{
|
||||
if(!validIdentChar(ch)) break;
|
||||
len++;
|
||||
}
|
||||
|
||||
char[] id = line[0..len];
|
||||
|
||||
// We only allow certain identifiers to begin with __, as
|
||||
// these are reserved for internal use.
|
||||
if(id.begins("__"))
|
||||
if(id != "__STACK__")
|
||||
fail("Identifier " ~ id ~ " is not allowed to begin with __");
|
||||
|
||||
// Check if this is a keyword
|
||||
if(id in keywordLookup)
|
||||
{
|
||||
TT t = keywordLookup[id];
|
||||
assert(t >= TT.Class && t < TT.Last,
|
||||
"Found " ~ id ~ " as a keyword, but with wrong type!");
|
||||
return retToken(t, tokenList[t]);
|
||||
}
|
||||
|
||||
// Not a keyword? Then it's an identifier
|
||||
return retToken(TT.Identifier, id.dup);
|
||||
}
|
||||
|
||||
// Check for operators and syntax characters. We browse through
|
||||
// the entire list, and select the longest match that fits (so
|
||||
// we don't risk matching "+" to "+=", for example.)
|
||||
TT match;
|
||||
int mlen = 0;
|
||||
foreach(int i, char[] tok; tokenList[0..TT.Class])
|
||||
{
|
||||
// Skip =i= and family, if monster.options tells us to
|
||||
static if(!ciStringOps)
|
||||
{
|
||||
if(i == TT.IsCaseEqual || i == TT.IsCaseEqual2 ||
|
||||
i == TT.NotCaseEqual || i == TT.NotCaseEqual2)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(line.begins(tok) && tok.length >= mlen)
|
||||
{
|
||||
assert(tok.length > mlen, "Two matching tokens of the same length");
|
||||
mlen = tok.length;
|
||||
match = cast(TT) i;
|
||||
}
|
||||
}
|
||||
|
||||
if(mlen) return retToken(match, tokenList[match]);
|
||||
|
||||
// Invalid token
|
||||
fail("Invalid token " ~ line);
|
||||
}
|
||||
|
||||
// Get the next token from a stream
|
||||
Token getNext()
|
||||
{
|
||||
assert(inf !is null, "getNext() found a null stream");
|
||||
|
||||
if(lineNum == -1) lineNum = 0;
|
||||
|
||||
restart:
|
||||
newline = false;
|
||||
// Get the next line, if the current is empty
|
||||
while(line.length == 0)
|
||||
{
|
||||
// No more information, we're done
|
||||
if(inf.eof())
|
||||
{
|
||||
if(mode == Block) fail("Unterminated block comment");
|
||||
if(mode == Nest) fail("Unterminated nested comment");
|
||||
return eofToken();
|
||||
}
|
||||
|
||||
// Read a line and remove leading and trailing whitespace
|
||||
line = inf.readLine(buffer).strip();
|
||||
lineNum++;
|
||||
newline = true;
|
||||
}
|
||||
|
||||
assert(line.length > 0);
|
||||
|
||||
static if(skipHashes)
|
||||
{
|
||||
// Skip the line if it begins with #.
|
||||
if(/*lineNum == 1 && */line.begins("#"))
|
||||
{
|
||||
line = null;
|
||||
goto restart;
|
||||
}
|
||||
}
|
||||
|
||||
Token tt = getNextFromLine();
|
||||
|
||||
// Skip empty lines, don't return them into the token list.
|
||||
if(tt.type == TT.EMPTY)
|
||||
goto restart;
|
||||
|
||||
return tt;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the entire file into an array of tokens. This includes the EOF
|
||||
// token at the end.
|
||||
TokenArray tokenizeStream(char[] fname, Stream stream, int bom)
|
||||
{
|
||||
TokenArray tokenArray;
|
||||
|
||||
Tokenizer tok = new Tokenizer(fname, stream, bom);
|
||||
Token tt;
|
||||
do
|
||||
{
|
||||
tt = tok.getNext();
|
||||
tokenArray ~= tt;
|
||||
}
|
||||
while(tt.type != TT.EOF)
|
||||
delete tok;
|
||||
|
||||
return tokenArray;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,58 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (all.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.modules.all;
|
||||
|
||||
import monster.modules.io;
|
||||
import monster.modules.math;
|
||||
import monster.modules.timer;
|
||||
import monster.modules.frames;
|
||||
import monster.modules.random;
|
||||
import monster.modules.threads;
|
||||
|
||||
import monster.options;
|
||||
|
||||
bool has(char[] str, char[] sub)
|
||||
{
|
||||
if(sub.length == 0) return false;
|
||||
|
||||
int diff = str.length;
|
||||
int sln = sub.length;
|
||||
|
||||
diff -= sln;
|
||||
for(int i=0; i<=diff; i++)
|
||||
if(str[i..i+sln] == sub[])
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void initAllModules()
|
||||
{
|
||||
static if(moduleList.has("io")) initIOModule();
|
||||
static if(moduleList.has("timer")) initTimerModule();
|
||||
static if(moduleList.has("frames")) initFramesModule();
|
||||
static if(moduleList.has("thread")) initThreadModule();
|
||||
static if(moduleList.has("random")) initRandomModule();
|
||||
static if(moduleList.has("math")) initMathModule();
|
||||
}
|
@ -1,553 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (console.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.modules.console;
|
||||
|
||||
// This file implements an optimized way of running Monster
|
||||
// interactively, what is sometimes called "line mode" or "interactive
|
||||
// mode". It is ideally suited for making ingame consoles.
|
||||
|
||||
// The main idea is to retain all reusable data structures and
|
||||
// minimize the number of heap allocations at runtime. The current
|
||||
// implemention is not perfected in that regard, but later
|
||||
// implementations will be.
|
||||
|
||||
// All input into the console is given through the input() function,
|
||||
// and all output is sent to the output() callback function. You can
|
||||
// also poll for output manually using output();
|
||||
|
||||
import monster.util.growarray;
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.statement;
|
||||
import monster.compiler.variables;
|
||||
import monster.compiler.functions;
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.bytecode;
|
||||
import monster.compiler.assembler;
|
||||
import monster.compiler.types;
|
||||
import monster.compiler.expression;
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import monster.monster;
|
||||
|
||||
// Console results
|
||||
enum CR
|
||||
{
|
||||
Ok = 1, // Command was executed
|
||||
Error = 2, // An error occurred
|
||||
More = 3, // An unterminated multi-line statement was entered, need
|
||||
// more input
|
||||
Empty = 4, // The line was empty (nothing was executed)
|
||||
}
|
||||
|
||||
class Console
|
||||
{
|
||||
private:
|
||||
Tokenizer tn;
|
||||
|
||||
GrowArray!(Token) tokArr;
|
||||
GrowArray!(char) outBuf;
|
||||
|
||||
Function fn;
|
||||
|
||||
FuncScope sc;
|
||||
|
||||
MonsterObject *obj;
|
||||
|
||||
Variable* varList[];
|
||||
uint varSize;
|
||||
|
||||
// The thread that we run console commands in. It's put in the
|
||||
// background when not in use.
|
||||
Thread *trd;
|
||||
|
||||
// The thread that was running when we started (if any)
|
||||
Thread *store;
|
||||
|
||||
int paren, curl, square;
|
||||
|
||||
void delegate(char[] str) output_cb;
|
||||
bool hasCallback;
|
||||
|
||||
char[] norm_prompt = ">>> ";
|
||||
int tab = 4;
|
||||
char[] ml_prompt = "... ";
|
||||
char[] cmt_prompt = "(comment) ";
|
||||
|
||||
public:
|
||||
bool allowVar = true;
|
||||
|
||||
this(MonsterObject *ob = null)
|
||||
{
|
||||
tn = new Tokenizer();
|
||||
|
||||
// Set the context object
|
||||
obj = ob;
|
||||
if(obj is null)
|
||||
obj = Function.getIntMO();
|
||||
|
||||
// Next set up the function and the scope
|
||||
fn.name.str = "__console";
|
||||
fn.owner = obj.cls;
|
||||
sc = new FuncScope(obj.cls.sc, &fn);
|
||||
|
||||
// Get a new thread
|
||||
trd = Thread.getPaused();
|
||||
}
|
||||
|
||||
void put(char[] str, bool newLine=false)
|
||||
{
|
||||
if(hasCallback)
|
||||
{
|
||||
output_cb(str);
|
||||
if(newLine)
|
||||
output_cb("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
outBuf ~= str;
|
||||
if(newLine)
|
||||
outBuf ~= '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void putln(char[] str) { put(str, true); }
|
||||
|
||||
private:
|
||||
Statement[] parse(TokenArray toks, Scope sc)
|
||||
{
|
||||
Statement b;
|
||||
Statement[] res;
|
||||
|
||||
repeat:
|
||||
|
||||
b = null;
|
||||
|
||||
if(CodeBlock.canParse(toks)) b = new CodeBlock;
|
||||
else if(IfStatement.canParse(toks)) b = new IfStatement;
|
||||
else if(DoWhileStatement.canParse(toks)) b = new DoWhileStatement;
|
||||
else if(WhileStatement.canParse(toks)) b = new WhileStatement;
|
||||
else if(ForStatement.canParse(toks)) b = new ForStatement;
|
||||
else if(ForeachStatement.canParse(toks)) b = new ForeachStatement;
|
||||
else if(ImportStatement.canParse(toks)) b = new ImportStatement(true);
|
||||
|
||||
if(b !is null)
|
||||
{
|
||||
// Parse and resolve
|
||||
b.parse(toks);
|
||||
b.resolve(sc);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this is not one of the above, default to a console
|
||||
// statement.
|
||||
auto es = new ConsoleStatement;
|
||||
b = es;
|
||||
|
||||
// Parse and resolve in one operation.
|
||||
es.parseResolve(toks, sc, allowVar);
|
||||
}
|
||||
|
||||
assert(b !is null);
|
||||
res ~= b;
|
||||
|
||||
// Are there more tokens waiting for us?
|
||||
if(toks.length > 0)
|
||||
goto repeat;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int sumParen()
|
||||
{
|
||||
// Clean up if we had an unmatched end bracket somewhere
|
||||
if(paren < 0) paren = 0;
|
||||
if(curl < 0) curl = 0;
|
||||
if(square < 0) square = 0;
|
||||
|
||||
return paren + curl + square;
|
||||
}
|
||||
|
||||
bool isComment()
|
||||
{
|
||||
return tn.mode != Tokenizer.Normal;
|
||||
}
|
||||
|
||||
// Resets the console to a usable state. Does not delete variables.
|
||||
void reset()
|
||||
{
|
||||
paren = 0;
|
||||
curl = 0;
|
||||
square = 0;
|
||||
tn.mode = Tokenizer.Normal;
|
||||
|
||||
if(cthread is trd)
|
||||
{
|
||||
// Reset the function stack.
|
||||
trd.fstack.killAll();
|
||||
|
||||
assert(trd.fstack.isEmpty);
|
||||
assert(!trd.fstack.hasNatives);
|
||||
|
||||
// Our variables should still be on the stack though.
|
||||
if(stack.getPos > varSize)
|
||||
stack.popInts(stack.getPos - varSize);
|
||||
else
|
||||
assert(stack.getPos == varSize);
|
||||
|
||||
// Make sure the thread is still in the 'paused' mode
|
||||
trd.moveTo(&scheduler.paused);
|
||||
|
||||
// Background the thread - this will also capture the stack
|
||||
trd.background();
|
||||
}
|
||||
|
||||
assert(trd !is cthread);
|
||||
|
||||
// Restore the previous thread (if any)
|
||||
if(store !is null)
|
||||
store.foreground();
|
||||
|
||||
store = null;
|
||||
}
|
||||
|
||||
// Push the input into the compiler and run it
|
||||
CR runInput(char[] str)
|
||||
{
|
||||
// Set up the tokenizer
|
||||
tn.setLine(str);
|
||||
|
||||
// Reset the token buffer, unless we're in a multiline command
|
||||
if(paren == 0 && curl == 0 && square == 0)
|
||||
tokArr.length = 0;
|
||||
|
||||
// Phase I, tokenize
|
||||
Token t = tn.getNextFromLine();
|
||||
// Mark the first token as a newline / separator
|
||||
t.newline = true;
|
||||
while(t.type != TT.EMPTY)
|
||||
{
|
||||
if(t.type == TT.LeftParen)
|
||||
paren++;
|
||||
else if(t.type == TT.LeftCurl)
|
||||
curl++;
|
||||
else if(t.type == TT.LeftSquare)
|
||||
square++;
|
||||
else if(t.type == TT.RightParen)
|
||||
paren--;
|
||||
else if(t.type == TT.RightCurl)
|
||||
curl--;
|
||||
else if(t.type == TT.RightSquare)
|
||||
square--;
|
||||
|
||||
tokArr ~= t;
|
||||
t = tn.getNextFromLine();
|
||||
}
|
||||
|
||||
if(paren < 0 || curl < 0 || square < 0)
|
||||
fail("Unmatched end bracket(s)");
|
||||
|
||||
// Wait for more input inside a bracket
|
||||
if(sumParen() > 0)
|
||||
return CR.More;
|
||||
|
||||
// Ditto for block comments
|
||||
if(isComment())
|
||||
return CR.More;
|
||||
|
||||
// Ignore empty token lists
|
||||
if(tokArr.length == 0)
|
||||
return CR.Empty;
|
||||
|
||||
// Phase II & III, parse and resolve
|
||||
TokenArray toks = tokArr.arrayCopy();
|
||||
Statement[] sts = parse(toks, sc);
|
||||
delete toks;
|
||||
assert(sts.length >= 1);
|
||||
|
||||
// First, background the current thread (if any) and bring up
|
||||
// our own. This is necessary in order to keep the stack
|
||||
// variables we make.
|
||||
store = cthread;
|
||||
if(store !is null)
|
||||
store.background();
|
||||
assert(trd !is null);
|
||||
trd.foreground();
|
||||
|
||||
// We have to push ourselves on the function stack, or
|
||||
// Function.call() will see that it's empty and kill the thread
|
||||
// upon exit
|
||||
trd.fstack.pushExt("Console");
|
||||
|
||||
// The rest must be performed separately for each statement on
|
||||
// the line.
|
||||
foreach(st; sts)
|
||||
{
|
||||
// Phase IV, compile
|
||||
tasm.newFunc();
|
||||
|
||||
// Expression to print, if any
|
||||
Expression printExp = null;
|
||||
|
||||
// Is it a special console statement?
|
||||
auto cs = cast(ConsoleStatement)st;
|
||||
if(cs !is null)
|
||||
{
|
||||
// Get the client expression, if any.
|
||||
ExprStatement es = cast(ExprStatement)cs.client;
|
||||
if(es !is null)
|
||||
{
|
||||
// It's a normal expression
|
||||
if(es.right is null)
|
||||
printExp = es.left;
|
||||
}
|
||||
// Not an expression, maybe a function?
|
||||
else if(cs.func !is null)
|
||||
{
|
||||
// Yup, store it
|
||||
printExp = cs.func;
|
||||
}
|
||||
}
|
||||
|
||||
// Ok, we got an expression. But is the type usable?
|
||||
if(printExp !is null)
|
||||
{
|
||||
auto tp = printExp.type;
|
||||
auto strt = ArrayType.getString();
|
||||
assert(tp !is null);
|
||||
assert(strt !is null);
|
||||
|
||||
if(tp == strt || tp.canCastTo(strt))
|
||||
{
|
||||
// Yup, it is! Cast the expression to string.
|
||||
strt.typeCast(printExp, "console output");
|
||||
printExp.eval();
|
||||
}
|
||||
else
|
||||
// Type isn't usable, so set printExp to null to flag
|
||||
// this.
|
||||
printExp = null;
|
||||
}
|
||||
|
||||
if(printExp is null)
|
||||
// No expression is being used, so compile the statement
|
||||
// normally.
|
||||
st.compile();
|
||||
|
||||
// Gather all the statements into one function and get the
|
||||
// bytecode.
|
||||
fn.bcode = tasm.assemble(fn.lines);
|
||||
fn.bcode ~= cast(ubyte)BC.Exit; // Non-optimal hack
|
||||
|
||||
// Phase V, call the function
|
||||
fn.call(obj);
|
||||
|
||||
// Finally, get the expression result, if any, and print it.
|
||||
if(printExp !is null)
|
||||
putln(stack.popString8());
|
||||
|
||||
// In the case of new a variable declaration, we have to
|
||||
// make sure they are accessible to any subsequent calls to
|
||||
// the function. Since the stack frame gets set at each
|
||||
// call, we have to access the variables outside the
|
||||
// function frame, ie. the same way we treat function
|
||||
// parameters. We do this by giving the variables negative
|
||||
// indices.
|
||||
if(cs !is null)
|
||||
{
|
||||
auto vs = cast(VarDeclStatement)cs.client;
|
||||
if(vs !is null)
|
||||
{
|
||||
// Add the new vars to the list
|
||||
foreach(v; vs.vars)
|
||||
{
|
||||
varList ~= v.var;
|
||||
|
||||
// Add the size as well
|
||||
varSize += v.var.type.getSize;
|
||||
}
|
||||
|
||||
// Recalculate all the indices backwards from zero
|
||||
int place = 0;
|
||||
foreach_reverse(v; varList)
|
||||
{
|
||||
place -= v.type.getSize;
|
||||
v.number = place;
|
||||
}
|
||||
|
||||
// Reset the scope stack counters
|
||||
sc.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trd.fstack.pop();
|
||||
|
||||
// Reset the console to a usable state
|
||||
reset();
|
||||
|
||||
return CR.Ok;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void prompt()
|
||||
{
|
||||
int sum = sumParen();
|
||||
bool isBracket = (sum != 0);
|
||||
|
||||
sum = sum * tab + norm_prompt.length;
|
||||
|
||||
if(isComment)
|
||||
{
|
||||
sum -= cmt_prompt.length;
|
||||
while(sum-->0)
|
||||
put(" ");
|
||||
put(cmt_prompt);
|
||||
}
|
||||
else if(isBracket)
|
||||
{
|
||||
sum -= ml_prompt.length;
|
||||
while(sum-->0)
|
||||
put(" ");
|
||||
put(ml_prompt);
|
||||
}
|
||||
else
|
||||
put(norm_prompt);
|
||||
}
|
||||
|
||||
void addImports(char[][] str...)
|
||||
{
|
||||
assert(sc !is null);
|
||||
sc.registerImport(str);
|
||||
}
|
||||
|
||||
// Get the accumulated output since the last call. Includes
|
||||
// newlines. Will only work if you have not set an output callback
|
||||
// function.
|
||||
char[] output()
|
||||
{
|
||||
assert(!hasCallback);
|
||||
char[] res = outBuf.arrayCopy();
|
||||
outBuf.length = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
// Sets the command prompt (default is "> ")
|
||||
void setPrompt(char[] prmt)
|
||||
{ norm_prompt = prmt; }
|
||||
|
||||
// Sets the multi-line prompt (default is "... ")
|
||||
void setMLPrompt(char[] prmt)
|
||||
{ ml_prompt = prmt; }
|
||||
|
||||
// Set tab size (default 4)
|
||||
void setTabSize(int i)
|
||||
{ tab = i; }
|
||||
|
||||
// Get input. Will sometimes expect multi-line input, for example if
|
||||
// case a line contains an open brace or an unterminated block
|
||||
// comment. In that case the console will produce another prompt
|
||||
// (when you call prompt()), and also return true.
|
||||
CR input(char[] str)
|
||||
{
|
||||
str = str.strip();
|
||||
if(str == "") return CR.Empty;
|
||||
|
||||
try return runInput(str);
|
||||
catch(MonsterException e)
|
||||
{
|
||||
putln(e.toString);
|
||||
reset();
|
||||
return CR.Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Statement that handles variable declarations, function calls and
|
||||
// expression statements in consoles. Since these are gramatically
|
||||
// similar, we need the type to determine which it is.
|
||||
class ConsoleStatement : Statement
|
||||
{
|
||||
// Used for variables and expression statements
|
||||
Statement client;
|
||||
|
||||
// Used for function calls
|
||||
FunctionCallExpr func;
|
||||
|
||||
void parseResolve(ref TokenArray toks, Scope sc, bool allowVar)
|
||||
{
|
||||
assert(toks.length != 0);
|
||||
|
||||
// Get the first expression
|
||||
auto first = Expression.identify(toks);
|
||||
|
||||
// And the type right away
|
||||
first.resolve(sc);
|
||||
auto type = first.type;
|
||||
assert(type !is null);
|
||||
|
||||
// Type? If so, it's a variable declaration
|
||||
if((type.isMeta || type.isVar) && allowVar)
|
||||
{
|
||||
if(type.isMeta) type = type.getBase();
|
||||
|
||||
client = new VarDeclStatement(type);
|
||||
}
|
||||
|
||||
// Function?
|
||||
else if(type.isIntFunc)
|
||||
func = new FunctionCallExpr(first, toks, true);
|
||||
|
||||
// It's an expression statement
|
||||
else
|
||||
client = new ExprStatement(first);
|
||||
|
||||
if(client !is null)
|
||||
{
|
||||
client.parse(toks);
|
||||
client.resolve(sc);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(func !is null);
|
||||
func.resolve(sc);
|
||||
}
|
||||
}
|
||||
|
||||
override:
|
||||
void parse(ref TokenArray) { assert(0); }
|
||||
void resolve(Scope sc) { assert(0); }
|
||||
|
||||
void compile()
|
||||
{
|
||||
if(client !is null)
|
||||
{
|
||||
client.compile();
|
||||
return;
|
||||
}
|
||||
|
||||
assert(func !is null);
|
||||
func.evalPop();
|
||||
}
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (frames.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// Provides some simple numbers and functions regarding the rendering
|
||||
// frames of the application. It's up to the user to some degree to
|
||||
// provide this information, though. We rely on vm.frame to be called
|
||||
// each frame.
|
||||
module monster.modules.frames;
|
||||
|
||||
import monster.monster;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.idlefunction;
|
||||
import monster.vm.thread;
|
||||
|
||||
const char[] moduleDef =
|
||||
"module frames;
|
||||
|
||||
float time; // Time since last frame
|
||||
float totalTime; // Time since rendering started
|
||||
|
||||
ulong counter; // Number of frames since program startup
|
||||
|
||||
// Sleep a given number of frames
|
||||
idle fsleep(int frameNum);
|
||||
"; //"
|
||||
|
||||
// Keep local copies of these, since we don't want Monster code to
|
||||
// overwrite them (we'll be able to explicitly forbid this later.)
|
||||
ulong frames = 0;
|
||||
float totTime = 0;
|
||||
|
||||
ulong *counter_ptr;
|
||||
float *time_ptr;
|
||||
float *totalTime_ptr;
|
||||
|
||||
// Add the given time and number of frames to the counters
|
||||
void updateFrames(float time, int frmCount = 1)
|
||||
{
|
||||
// Add up to the totals
|
||||
frames += frmCount;
|
||||
totTime += time;
|
||||
|
||||
// Set the Monster variables
|
||||
*counter_ptr = frames;
|
||||
*time_ptr = time;
|
||||
*totalTime_ptr = totTime;
|
||||
|
||||
// TODO: A similar priority queue like we're planning for timer
|
||||
// would also be applicable here. However I'm guessing frameSleep()
|
||||
// will be used a lot less than sleep() though, so this is really
|
||||
// not high up on the priority list.
|
||||
}
|
||||
|
||||
// Idle function that sleeps a given number of frames before
|
||||
// returning.
|
||||
class IdleFrameSleep : IdleFunction
|
||||
{
|
||||
override:
|
||||
IS initiate(Thread* cn)
|
||||
{
|
||||
// Calculate the return frame
|
||||
cn.idleData.l = frames + stack.popInt;
|
||||
|
||||
// Schedule us
|
||||
return IS.Poll;
|
||||
}
|
||||
|
||||
bool hasFinished(Thread* cn)
|
||||
{
|
||||
// Are we at (or past) the correct frame?
|
||||
return frames >= cn.idleData.l;
|
||||
}
|
||||
}
|
||||
|
||||
void initFramesModule()
|
||||
{
|
||||
static MonsterClass mc;
|
||||
if(mc !is null) return;
|
||||
|
||||
mc = vm.loadString(moduleDef, "frames");
|
||||
|
||||
// Bind the idle
|
||||
mc.bind("fsleep", new IdleFrameSleep);
|
||||
|
||||
// Get pointers to the variables so we can write to them easily.
|
||||
auto mo = mc.getSing();
|
||||
counter_ptr = mo.getUlongPtr("counter");
|
||||
time_ptr = mo.getFloatPtr("time");
|
||||
totalTime_ptr = mo.getFloatPtr("totalTime");
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
module frames;
|
||||
|
||||
float time; // Time since last frame
|
||||
float totalTime; // Time since rendering started
|
||||
|
||||
ulong counter; // Number of frames since program startup
|
||||
|
||||
// Sleep a given number of frames
|
||||
idle fsleep(int frameNum);
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (io.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// This module provides simple output functions for Monster. The 'i'
|
||||
// (input) part of 'io' isn't really there yet.
|
||||
module monster.modules.io;
|
||||
|
||||
import monster.monster;
|
||||
|
||||
const char[] moduleDef =
|
||||
"module io;
|
||||
|
||||
native write(char[][] args...);
|
||||
native writeln(char[][] args...);
|
||||
native writes(char[][] args...);
|
||||
native writelns(char[][] args...);
|
||||
native print(char[][] args...);
|
||||
"; //"
|
||||
|
||||
// Use tango library functions directly, since flushing the
|
||||
// minibos-versions will give weird effects when mixing with other
|
||||
// tango output.
|
||||
version(Tango)
|
||||
{
|
||||
import tango.io.Stdout;
|
||||
void doWrite(bool space)
|
||||
{
|
||||
AIndex[] args = stack.popAArray();
|
||||
|
||||
char[] form = "{}";
|
||||
if(space) form = "{} ";
|
||||
|
||||
foreach(AIndex ind; args)
|
||||
Stdout.format(form, arrays.getRef(ind).carr);
|
||||
|
||||
Stdout.flush();
|
||||
}
|
||||
void writefln() { Stdout.newline; }
|
||||
}
|
||||
else
|
||||
{ // Phobos
|
||||
import std.stdio;
|
||||
void doWrite(bool space)
|
||||
{
|
||||
AIndex[] args = stack.popAArray();
|
||||
|
||||
char[] form = "%s";
|
||||
if(space) form = "%s ";
|
||||
|
||||
foreach(AIndex ind; args)
|
||||
writef(form, arrays.getRef(ind).carr);
|
||||
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
void initIOModule()
|
||||
{
|
||||
static MonsterClass mc;
|
||||
if(mc !is null) return;
|
||||
|
||||
mc = vm.loadString(moduleDef, "io");
|
||||
|
||||
mc.bind("write", { doWrite(false); });
|
||||
mc.bind("writeln", { doWrite(false); writefln(); });
|
||||
mc.bind("writes", { doWrite(true); });
|
||||
mc.bind("writelns", { doWrite(true); writefln(); });
|
||||
|
||||
// Print is just another name for writelns
|
||||
mc.bind("print", { doWrite(true); writefln(); });
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
/*
|
||||
NOTE: This file is not used - it is here just for reference. The
|
||||
real module is defined internally in io.d.
|
||||
*/
|
||||
|
||||
module io;
|
||||
|
||||
// Write to console, with or without a newline
|
||||
native write(char[][] args...);
|
||||
native writeln(char[][] args...);
|
||||
|
||||
// Automatically inserts spaces between arguments
|
||||
native writes(char[][] args...);
|
||||
native writelns(char[][] args...);
|
||||
|
||||
// Identical to writelns
|
||||
native print(char[][] args...);
|
@ -1,166 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (math.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// Simple math functions
|
||||
module monster.modules.math;
|
||||
|
||||
import monster.monster;
|
||||
import std.math;
|
||||
import monster.vm.mclass;
|
||||
|
||||
const char[] moduleDef =
|
||||
"module math;
|
||||
|
||||
double E = 2.7182818284590452354;
|
||||
double PI = 3.141592653589793238;
|
||||
double DEGTORAD = 3.141592653590/180;
|
||||
double RADTODEG = 180/3.141592653590;
|
||||
|
||||
native double sin(double x);
|
||||
native double cos(double x);
|
||||
native double tan(double x);
|
||||
native double asin(double x);
|
||||
native double acos(double x);
|
||||
native double atan(double x);
|
||||
native double atan2(double y, double x); // = atan(y/x)
|
||||
native double sinh(double x);
|
||||
native double cosh(double x);
|
||||
native double tanh(double x);
|
||||
native double asinh(double x);
|
||||
native double acosh(double x);
|
||||
native double atanh(double x);
|
||||
|
||||
native double sqrt(double x);
|
||||
native double exp(double x); // e^x
|
||||
native double exp2(double x); // 2^x
|
||||
native double log(double x); // base e
|
||||
native double log10(double x);// base 10
|
||||
native double log2(double x); // base 2
|
||||
|
||||
native double pow(double x, double y); // x^y
|
||||
native double ipow(double x, int n); // x^n (faster than pow)
|
||||
|
||||
native int abs(int x);
|
||||
native double fabs(double x);
|
||||
native double ceil(double x);
|
||||
native double floor(double x);
|
||||
native double round(double x); // rounds to nearest integer
|
||||
native double trunc(double x);
|
||||
|
||||
native double hypot(double x, double y); // = sqrt(x*x+y*y)
|
||||
native double cbrt(double x); // cube root
|
||||
|
||||
// Calculates polynomial a0 + x*a1 + x^2*a2 + x^3*a3 + ...
|
||||
native double poly(double x, float[] A);
|
||||
"; //"
|
||||
|
||||
version(Tango)
|
||||
{
|
||||
double fabs(double x) { return abs(x); }
|
||||
}
|
||||
|
||||
void initMathModule()
|
||||
{
|
||||
static MonsterClass mc;
|
||||
if(mc !is null) return;
|
||||
|
||||
mc = vm.loadString(moduleDef, "math");
|
||||
|
||||
mc.bind("sin", { stack.pushDouble(sin(stack.popDouble)); });
|
||||
mc.bind("cos", { stack.pushDouble(cos(stack.popDouble)); });
|
||||
mc.bind("tan", { stack.pushDouble(tan(stack.popDouble)); });
|
||||
mc.bind("asin", { stack.pushDouble(asin(stack.popDouble)); });
|
||||
mc.bind("acos", { stack.pushDouble(acos(stack.popDouble)); });
|
||||
mc.bind("atan", { stack.pushDouble(atan(stack.popDouble)); });
|
||||
|
||||
mc.bind("sinh", { stack.pushDouble(sinh(stack.popDouble)); });
|
||||
mc.bind("cosh", { stack.pushDouble(cosh(stack.popDouble)); });
|
||||
mc.bind("tanh", { stack.pushDouble(tanh(stack.popDouble)); });
|
||||
mc.bind("asinh", { stack.pushDouble(asinh(stack.popDouble)); });
|
||||
mc.bind("acosh", { stack.pushDouble(acosh(stack.popDouble)); });
|
||||
mc.bind("atanh", { stack.pushDouble(atanh(stack.popDouble)); });
|
||||
|
||||
mc.bind("atan2",
|
||||
{
|
||||
// Remember to pop the variables in the reverse order
|
||||
auto x = stack.popDouble;
|
||||
stack.pushDouble(atan2(stack.popDouble, x));
|
||||
});
|
||||
|
||||
mc.bind("sqrt", { stack.pushDouble(sqrt(stack.popDouble)); });
|
||||
mc.bind("exp", { stack.pushDouble(exp(stack.popDouble)); });
|
||||
mc.bind("exp2", { stack.pushDouble(exp2(stack.popDouble)); });
|
||||
mc.bind("log", { stack.pushDouble(log(stack.popDouble)); });
|
||||
mc.bind("log2", { stack.pushDouble(log2(stack.popDouble)); });
|
||||
mc.bind("log10", { stack.pushDouble(log10(stack.popDouble)); });
|
||||
|
||||
mc.bind("pow",
|
||||
{
|
||||
auto x = stack.popDouble;
|
||||
stack.pushDouble(pow(stack.popDouble, x));
|
||||
});
|
||||
mc.bind("ipow",
|
||||
{
|
||||
auto x = stack.popInt;
|
||||
stack.pushDouble(pow(cast(real)stack.popDouble, x));
|
||||
});
|
||||
|
||||
mc.bind("abs", { stack.pushDouble(abs(stack.popDouble)); });
|
||||
mc.bind("fabs", { stack.pushDouble(fabs(stack.popDouble)); });
|
||||
mc.bind("ceil", { stack.pushDouble(ceil(stack.popDouble)); });
|
||||
mc.bind("floor", { stack.pushDouble(floor(stack.popDouble)); });
|
||||
mc.bind("round", { stack.pushDouble(round(stack.popDouble)); });
|
||||
mc.bind("trunc", { stack.pushDouble(trunc(stack.popDouble)); });
|
||||
|
||||
// Order doesn't matter here
|
||||
mc.bind("hypot", { stack.pushDouble(hypot(stack.popDouble,
|
||||
stack.popDouble)); });
|
||||
|
||||
mc.bind("cbrt", { stack.pushDouble(cbrt(stack.popDouble)); });
|
||||
|
||||
mc.bind("poly", &npoly);
|
||||
}
|
||||
|
||||
// Implement this ourselves, since phobos doesn't use the types we
|
||||
// want
|
||||
double poly(double x, float A[])
|
||||
{
|
||||
// Use 'real' internally for higher precision
|
||||
real r = A[$-1];
|
||||
foreach_reverse(c; A[0..$-1])
|
||||
{
|
||||
r *= x;
|
||||
r += c;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// double poly(double x, float[] A);
|
||||
void npoly()
|
||||
{
|
||||
auto arf = stack.popArray();
|
||||
assert(arf.elemSize == 1);
|
||||
stack.pushDouble(poly(stack.popDouble, arf.farr));
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
module math;
|
||||
|
||||
double E = 2.7182818284590452354;
|
||||
double PI = 3.141592653589793238;
|
||||
double DEGTORAD = 3.141592653590/180;
|
||||
double RADTODEG = 180/3.141592653590;
|
||||
|
||||
native double sin(double x);
|
||||
native double cos(double x);
|
||||
native double tan(double x);
|
||||
native double asin(double x);
|
||||
native double acos(double x);
|
||||
native double atan(double x);
|
||||
native double atan2(double y, double x); // = atan(y/x)
|
||||
native double sinh(double x);
|
||||
native double cosh(double x);
|
||||
native double tanh(double x);
|
||||
native double asinh(double x);
|
||||
native double acosh(double x);
|
||||
native double atanh(double x);
|
||||
|
||||
native double sqrt(double x);
|
||||
native double exp(double x); // e^x
|
||||
native double exp2(double x); // 2^x
|
||||
native double log(double x); // base e
|
||||
native double log10(double x);// base 10
|
||||
native double log2(double x); // base 2
|
||||
|
||||
native double pow(double x, double y); // x^y
|
||||
native double ipow(double x, int n); // x^n (faster than pow)
|
||||
|
||||
native int abs(int x);
|
||||
native double fabs(double x);
|
||||
native double ceil(double x);
|
||||
native double floor(double x);
|
||||
native double round(double x); // rounds to nearest integer
|
||||
native double trunc(double x);
|
||||
|
||||
native double hypot(double x, double y); // = sqrt(x*x+y*y)
|
||||
native double cbrt(double x); // cube root
|
||||
|
||||
// Calculates polynomial a0 + x*a1 + x^2*a2 + x^3*a3 + ...
|
||||
native double poly(double x, float[] A);
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (random.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// This module provides simple random number generation. Since this is
|
||||
// intended for game development, speed and simplicity is favored over
|
||||
// flexibility and random number quality.
|
||||
module monster.modules.random;
|
||||
|
||||
import monster.monster;
|
||||
import std.random;
|
||||
|
||||
const char[] moduleDef =
|
||||
"module random;
|
||||
|
||||
native uint rand(); // Return a number between 0 and uint.max, inclusive
|
||||
native float frand(); // Return a number between 0 and 1, inclusive
|
||||
|
||||
// Return a random number between a and b, inclusive. Allows negative
|
||||
// numbers, and works with a>b, a<b and a==b
|
||||
native int randInt(int a, int b);
|
||||
"; //"
|
||||
|
||||
const float _frandFactor = 1.0/uint.max;
|
||||
|
||||
// Return a random integer between a and b, inclusive.
|
||||
int randInt(int a, int b)
|
||||
out(result)
|
||||
{
|
||||
// Result must be in range m <= result <= M, where m=min(a,b) and M=max(a,b)
|
||||
if(b >= a) assert( (a <= result) && (result <= b) );
|
||||
else if(a > b) assert( (b <= result) && (result <= a) );
|
||||
}
|
||||
body
|
||||
{
|
||||
if(a>b) return cast(int)(rand() % (a-b+1)) + b;
|
||||
else if(b>a) return cast(int)(rand() % (b-a+1)) + a;
|
||||
else return a;
|
||||
}
|
||||
|
||||
void initRandomModule()
|
||||
{
|
||||
static MonsterClass mc;
|
||||
if(mc !is null) return;
|
||||
|
||||
mc = vm.loadString(moduleDef, "random");
|
||||
|
||||
mc.bind("rand", { stack.pushInt(rand()); });
|
||||
mc.bind("frand", { stack.pushFloat(rand()*_frandFactor); });
|
||||
mc.bind("randInt", { stack.pushInt(randInt(stack.popInt,
|
||||
stack.popInt)); });
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
module random;
|
||||
|
||||
native uint rand(); // Return a number between 0 and uint.max, inclusive
|
||||
native float frand(); // Return a number between 0 and 1, inclusive
|
||||
|
||||
// Return a random number between a and b, inclusive. Allows negative
|
||||
// numbers, and works with a>b, a<b and a==b
|
||||
native int randInt(int a, int b);
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
NOTE: This file is not used - it is here just for reference. The
|
||||
real module is defined internally in io.d.
|
||||
*/
|
||||
|
||||
singleton thread;
|
||||
|
||||
// Used to kill or pause our own or other threads.
|
||||
idle kill();
|
||||
idle pause();
|
||||
|
||||
// Get status information about a thread
|
||||
native bool isScheduled();
|
||||
native bool isPaused();
|
||||
native bool isIdle();
|
||||
native bool isDead();
|
||||
bool isAlive() { return !isDead(); }
|
||||
|
||||
// Create a new (paused) thread for a given function
|
||||
native thread create(function() f);
|
||||
|
||||
// Schedule a (paused) thread to run the next frame
|
||||
native restart();
|
||||
|
||||
// Call a (paused) thread directly - returns when the thread exits or
|
||||
// calls an idle function.
|
||||
idle call();
|
||||
|
||||
// Wait for a thread to finish. Will not return until the thread is
|
||||
// dead.
|
||||
idle wait();
|
||||
|
||||
// Start a function as a thread in the background
|
||||
thread start(function() f)
|
||||
{
|
||||
var t = create(f);
|
||||
t.restart();
|
||||
return t;
|
||||
}
|
@ -1,279 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (threads.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// This module provides an interface to the virtual threading API in
|
||||
// Monster.
|
||||
|
||||
module monster.modules.threads;
|
||||
|
||||
import monster.monster;
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.idlefunction;
|
||||
import monster.vm.thread;
|
||||
import monster.vm.mclass;
|
||||
import monster.compiler.functions;
|
||||
import std.stdio;
|
||||
|
||||
const char[] moduleDef =
|
||||
"singleton thread;
|
||||
|
||||
// Used to kill or pause our own or other threads.
|
||||
idle kill();
|
||||
idle pause();
|
||||
|
||||
// Get status information about a thread
|
||||
native bool isScheduled();
|
||||
native bool isPaused();
|
||||
native bool isIdle();
|
||||
native bool isDead();
|
||||
bool isAlive() { return !isDead(); }
|
||||
|
||||
// Create a new (paused) thread for a given function
|
||||
native thread create(function() f);
|
||||
|
||||
// Schedule a (paused) thread to run the next frame
|
||||
native restart();
|
||||
|
||||
// Call a (paused) thread directly - returns when the thread exits or
|
||||
// calls an idle function.
|
||||
idle call();
|
||||
|
||||
// Wait for a thread to finish. Will not return until the thread is
|
||||
// dead.
|
||||
idle wait();
|
||||
|
||||
// Start a function as a thread in the background
|
||||
thread start(function() f)
|
||||
{
|
||||
var t = create(f);
|
||||
t.restart();
|
||||
return t;
|
||||
}"; //"
|
||||
|
||||
/*
|
||||
The char[] name stuff above will of course be replaced with real
|
||||
function pointers once those are done. When closures are done we
|
||||
will also add:
|
||||
|
||||
function() wrap(function() f)
|
||||
{
|
||||
var t = create(f);
|
||||
return { t.call(); }
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
MonsterObject *trdSing;
|
||||
|
||||
class Kill : IdleFunction
|
||||
{
|
||||
IS initiate(Thread *t)
|
||||
{
|
||||
auto mo = params.obj;
|
||||
|
||||
if(mo !is trdSing)
|
||||
{
|
||||
// Check if this is another thread
|
||||
auto trd = getOwner(mo);
|
||||
if(trd !is t)
|
||||
{
|
||||
// It is. Kill it explicitly and return.
|
||||
trd.kill();
|
||||
return IS.Return;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's our own thread, tell the scheduler to kill it from
|
||||
// the inside.
|
||||
return IS.Kill;
|
||||
}
|
||||
}
|
||||
|
||||
class Pause : IdleFunction
|
||||
{
|
||||
IS initiate(Thread *t)
|
||||
{
|
||||
auto mo = params.obj;
|
||||
|
||||
// Can only run on the singleton object
|
||||
if(mo !is trdSing)
|
||||
fail("Can only pause our own thread");
|
||||
|
||||
// Move the thread to the 'paused' list
|
||||
t.moveTo(&scheduler.paused);
|
||||
|
||||
return IS.Manual;
|
||||
}
|
||||
}
|
||||
|
||||
Thread *getOwner() { return getOwner(params.obj); }
|
||||
Thread *getOwner(MonsterObject *mo)
|
||||
{
|
||||
assert(mo !is null);
|
||||
if(mo is trdSing)
|
||||
fail("Cannot run this function on the global singleton thread.");
|
||||
auto trd = cast(Thread*) mo.getExtra(_threadClass).vptr;
|
||||
assert(trd !is null);
|
||||
assert(!trd.isRunning || !trd.isPaused,
|
||||
"thread cannot be running and paused at the same time");
|
||||
return trd;
|
||||
}
|
||||
|
||||
MonsterObject *createObj(Thread *trd)
|
||||
{
|
||||
assert(trd !is null);
|
||||
auto mo = _threadClass.createObject();
|
||||
mo.getExtra(_threadClass).vptr = trd;
|
||||
return mo;
|
||||
}
|
||||
|
||||
void create()
|
||||
{
|
||||
// Can only run on the singleton object
|
||||
if(params.obj !is trdSing)
|
||||
fail("Can only use create() on the global thread object.");
|
||||
|
||||
auto fn = stack.popFuncRef();
|
||||
auto trd = fn.getObject().thread(fn.getFunction());
|
||||
|
||||
stack.pushObject(createObj(trd));
|
||||
}
|
||||
|
||||
// Call is used to restore a thread that was previously paused. It
|
||||
// will enter the thread immediately, like a normal function call, but
|
||||
// it will still run in its own thread. If you only wish to schedule
|
||||
// it for later, use restart instead.
|
||||
class Call : IdleFunction
|
||||
{
|
||||
override:
|
||||
IS initiate(Thread *t)
|
||||
{
|
||||
if(params.obj is trdSing)
|
||||
fail("Cannot use call() on our own thread.");
|
||||
|
||||
// Get the thread we're resuming
|
||||
auto trd = getOwner();
|
||||
|
||||
if(trd is t)
|
||||
fail("Cannot use call() on our own thread.");
|
||||
|
||||
if(trd.isDead)
|
||||
fail("Cannot call a dead thread.");
|
||||
|
||||
if(!trd.isPaused)
|
||||
fail("Can only use call() on paused threads");
|
||||
|
||||
// Background the current thread. Move it to the pause list
|
||||
// first, so background doesn't inadvertently delete it.
|
||||
t.moveTo(&scheduler.paused);
|
||||
t.background();
|
||||
assert(!t.isDead);
|
||||
|
||||
// Reenter the thread
|
||||
trd.reenter();
|
||||
assert(cthread is null);
|
||||
|
||||
// Put the old thread in the forground again
|
||||
t.foreground();
|
||||
|
||||
// Make the thread transient again
|
||||
t.moveTo(&scheduler.transient);
|
||||
|
||||
// Return to sender
|
||||
return IS.Return;
|
||||
}
|
||||
|
||||
void abort(Thread *t)
|
||||
{
|
||||
fail("Cannot abort thread while it is calling another thread");
|
||||
}
|
||||
}
|
||||
|
||||
class Wait : IdleFunction
|
||||
{
|
||||
override:
|
||||
IS initiate(Thread *t)
|
||||
{
|
||||
if(params.obj is trdSing)
|
||||
fail("Cannot use wait on our own thread.");
|
||||
|
||||
// Get the thread we're resuming
|
||||
auto trd = getOwner();
|
||||
|
||||
if(trd is t)
|
||||
fail("Cannot use wait on our own thread.");
|
||||
|
||||
// Return immediately if the thread is dead
|
||||
if(trd.isDead)
|
||||
return IS.Return;
|
||||
|
||||
t.idleData.vptr = trd;
|
||||
|
||||
return IS.Poll;
|
||||
}
|
||||
|
||||
bool hasFinished(Thread *t)
|
||||
{
|
||||
return (cast(Thread*)t.idleData.vptr).isDead;
|
||||
}
|
||||
}
|
||||
|
||||
void restart()
|
||||
{ getOwner().restart(); }
|
||||
|
||||
void isDead()
|
||||
{ stack.pushBool(getOwner().isDead); }
|
||||
|
||||
void isIdle()
|
||||
{ stack.pushBool(getOwner().fstack.isIdle); }
|
||||
|
||||
void isPaused()
|
||||
{ stack.pushBool(getOwner().isPaused); }
|
||||
|
||||
void isScheduled()
|
||||
{ stack.pushBool(getOwner().isScheduled); }
|
||||
|
||||
MonsterClass _threadClass;
|
||||
|
||||
void initThreadModule()
|
||||
{
|
||||
if(_threadClass !is null)
|
||||
return;
|
||||
|
||||
_threadClass = vm.loadString(moduleDef, "thread");
|
||||
trdSing = _threadClass.getSing();
|
||||
|
||||
_threadClass.bind("kill", new Kill);
|
||||
_threadClass.bind("call", new Call);
|
||||
_threadClass.bind("pause", new Pause);
|
||||
_threadClass.bind("wait", new Wait);
|
||||
|
||||
_threadClass.bind("create", &create);
|
||||
_threadClass.bind("restart", &restart);
|
||||
|
||||
_threadClass.bind("isDead", &isDead);
|
||||
_threadClass.bind("isIdle", &isIdle);
|
||||
_threadClass.bind("isPaused", &isPaused);
|
||||
_threadClass.bind("isScheduled", &isScheduled);
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (timer.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// This module contains (or will contain) various routines for
|
||||
// timing. It is also home of the ubiquitous "sleep" idle function.
|
||||
|
||||
module monster.modules.timer;
|
||||
|
||||
import std.stdio;
|
||||
|
||||
// For some utterly idiotic reason, DMD's public imports will suddenly
|
||||
// stop working from time to time.
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.stack;
|
||||
import monster.vm.thread;
|
||||
import monster.vm.idlefunction;
|
||||
|
||||
import monster.monster;
|
||||
import monster.options;
|
||||
|
||||
const char[] moduleDef =
|
||||
"singleton timer;
|
||||
idle sleep(float secs);
|
||||
"; //"
|
||||
|
||||
static if(timer_useClock)
|
||||
{
|
||||
// Sleep a given amount of time. This implementation uses the system
|
||||
// clock.
|
||||
import std.date;
|
||||
class IdleSleep_SystemClock : IdleFunction
|
||||
{
|
||||
override:
|
||||
IS initiate(Thread* cn)
|
||||
{
|
||||
// Get the parameter
|
||||
float secs = stack.popFloat;
|
||||
|
||||
// Get current time
|
||||
cn.idleData.l = getUTCtime();
|
||||
|
||||
// Calculate when we should return
|
||||
cn.idleData.l += secs*TicksPerSecond;
|
||||
|
||||
// Schedule us
|
||||
return IS.Poll;
|
||||
}
|
||||
|
||||
bool hasFinished(Thread* cn)
|
||||
{
|
||||
// Is it time?
|
||||
return getUTCtime() >= cn.idleData.l;
|
||||
}
|
||||
}
|
||||
|
||||
} else { // If timer_useClock is NOT set:
|
||||
|
||||
// This implementation uses a user-driven timer instead of the system
|
||||
// clock. It's more efficient, but requires the user to update the
|
||||
// given timer manually each frame. The default sleep (timer.sleep) is
|
||||
// bound to the default timer, but it's possible to create multiple
|
||||
// independent timers.
|
||||
class IdleSleep_Timer : IdleFunction
|
||||
{
|
||||
override:
|
||||
IS initiate(Thread* cn)
|
||||
{
|
||||
// The timer is stored in the object's 'extra' pointer
|
||||
auto t = cast(SleepManager)cn.extraData.obj;
|
||||
assert(t !is null);
|
||||
|
||||
// Calculate the return time
|
||||
cn.idleData.l = t.current + cast(long)(t.tickSize*stack.popFloat);
|
||||
|
||||
// Schedule us
|
||||
return IS.Poll;
|
||||
}
|
||||
|
||||
bool hasFinished(Thread* cn)
|
||||
{
|
||||
// Get the timer
|
||||
auto t = cast(SleepManager)cn.extraData.obj;
|
||||
assert(t !is null);
|
||||
|
||||
// Is it time?
|
||||
return t.current >= cn.idleData.l;
|
||||
}
|
||||
}
|
||||
|
||||
// A manually updated timer. This can be improved quite a lot: Most
|
||||
// sleep operations (depending on application of course) will skip
|
||||
// many frames before they return. For example, for sleep(0.5) at 100
|
||||
// fps, hasFinished will return false approximately 50 times before
|
||||
// returning true. For bigger sleep values and a large number of
|
||||
// objects, the impact of this is significant. A good solution would
|
||||
// be to pool scheduled objects together and only perform one check on
|
||||
// the entire pool. If the pool is due, all the nodes within it are
|
||||
// inserted into the scheduler for detailed checking. We could have a
|
||||
// series of such pools, ordered by expiration time, so that we only
|
||||
// ever need to check the first pool in the list. The optimal pool
|
||||
// interval, number of pools etc depends on the application and the
|
||||
// fps - but it should be possible to find some reasonable defaults. A
|
||||
// more generalized priority queue implementation is also possible.
|
||||
class SleepManager
|
||||
{
|
||||
private:
|
||||
// Instance of the timer class that is associated with this timer
|
||||
MonsterObject *tobj;
|
||||
|
||||
// Current tick count
|
||||
long current;
|
||||
|
||||
public:
|
||||
|
||||
// Specify a Monster object to associate with this timer. Use 'null'
|
||||
// if you don't need an object.
|
||||
this(MonsterObject *obj)
|
||||
{
|
||||
if(obj is null) return;
|
||||
|
||||
tobj = obj;
|
||||
tobj.getExtra(_timerClass).obj = this;
|
||||
}
|
||||
|
||||
// By default, create a new object
|
||||
this()
|
||||
{ this(_timerClass.createObject); }
|
||||
|
||||
// Number of 'ticks' per second
|
||||
static const long tickSize = 1000;
|
||||
|
||||
// Reset the timer to zero
|
||||
void reset() { current = 0; }
|
||||
|
||||
// Return the total number of elapsed seconds since start (or last
|
||||
// reset)
|
||||
double read() { return current/cast(double)tickSize; }
|
||||
|
||||
// Add time to the timer.
|
||||
void add(double d) { current += cast(long)(tickSize*d); }
|
||||
void addl(long l) { current += l; }
|
||||
|
||||
MonsterObject *getObj() { return tobj; }
|
||||
}
|
||||
|
||||
SleepManager idleTime;
|
||||
}
|
||||
|
||||
MonsterClass _timerClass;
|
||||
|
||||
void initTimerModule()
|
||||
{
|
||||
if(_timerClass !is null)
|
||||
return;
|
||||
|
||||
_timerClass = vm.loadString(moduleDef, "timer");
|
||||
|
||||
static if(timer_useClock)
|
||||
{
|
||||
_timerClass.bind("sleep", new IdleSleep_SystemClock);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(idleTime is null);
|
||||
idleTime = new SleepManager(_timerClass.getSing());
|
||||
_timerClass.bind("sleep", new IdleSleep_Timer);
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
/*
|
||||
NOTE: This file is not used - it is here just for reference. The
|
||||
real module is defined internally in timer.d.
|
||||
*/
|
||||
|
||||
singleton timer;
|
||||
|
||||
// Sleep the given number of seconds
|
||||
idle sleep(float secs);
|
@ -1,183 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (vfs.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.modules.vfs;
|
||||
|
||||
import std.file;
|
||||
import std.stream;
|
||||
import std.string;
|
||||
import monster.util.string;
|
||||
import monster.vm.error;
|
||||
|
||||
abstract class VFS
|
||||
{
|
||||
// Abstract functions. These must be implemented in child classes.
|
||||
|
||||
// Return true if a file exists. Should not return true for
|
||||
// directories.
|
||||
abstract bool has(char[] file);
|
||||
abstract bool hasDir(char[] dir);
|
||||
|
||||
// Open the given file and return it as a stream.
|
||||
abstract Stream open(char[] file);
|
||||
|
||||
// Check for invalid file names. This makes sure the caller cannot
|
||||
// read files outside the designated subdirectory.
|
||||
final static void checkForEscape(char[] file)
|
||||
{
|
||||
if(file.begins("/") || file.begins("\\"))
|
||||
fail("Filename " ~ file ~ " cannot begin with a path separator");
|
||||
if(file.find(":") != -1)
|
||||
fail("Filename " ~ file ~ " cannot contain colons");
|
||||
if(file.find("..") != -1)
|
||||
fail("Filename " ~ file ~ " cannot contain '..'");
|
||||
}
|
||||
}
|
||||
|
||||
// A VFS that contains a list of other VFS objects
|
||||
class ListVFS : VFS
|
||||
{
|
||||
private:
|
||||
VFS[] list;
|
||||
|
||||
public:
|
||||
this(VFS v[] ...)
|
||||
{ list = v; }
|
||||
|
||||
void add(VFS v[] ...)
|
||||
{ list ~= v; }
|
||||
|
||||
void addFirst(VFS v[] ...)
|
||||
{ list = v ~ list; }
|
||||
|
||||
bool has(char[] file)
|
||||
{
|
||||
foreach(l; list)
|
||||
if(l.has(file)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hasDir(char[] file)
|
||||
{
|
||||
foreach(l; list)
|
||||
if(l.hasDir(file)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
Stream open(char[] file)
|
||||
{
|
||||
foreach(l; list)
|
||||
if(l.has(file)) return l.open(file);
|
||||
fail("No member VFS contains file " ~ file);
|
||||
}
|
||||
}
|
||||
|
||||
// A VFS that reads files from a given path in the OS file
|
||||
// system. Disallows filenames that escape the given path,
|
||||
// ie. filenames such as:
|
||||
//
|
||||
// /etc/passwd
|
||||
// dir/../../file
|
||||
// c:\somefile
|
||||
class FileVFS : VFS
|
||||
{
|
||||
private:
|
||||
char[] sysPath;
|
||||
char[] buffer;
|
||||
|
||||
char[] getPath(char[] file)
|
||||
{
|
||||
// Make sure the buffer is large enough
|
||||
if(buffer.length < file.length+sysPath.length)
|
||||
buffer.length = file.length + sysPath.length + 50;
|
||||
|
||||
checkForEscape(file);
|
||||
|
||||
// Copy the file name over
|
||||
buffer[sysPath.length .. sysPath.length+file.length]
|
||||
= file[];
|
||||
|
||||
// Convert the path characters
|
||||
convPath();
|
||||
|
||||
// Return the result
|
||||
return buffer[0..sysPath.length+file.length];
|
||||
}
|
||||
|
||||
// Convert path separators
|
||||
void convPath()
|
||||
{
|
||||
foreach(ref c; buffer)
|
||||
if(c == from)
|
||||
c = to;
|
||||
}
|
||||
|
||||
version(Windows)
|
||||
{
|
||||
const char from = '/';
|
||||
const char to = '\\';
|
||||
}
|
||||
else
|
||||
{
|
||||
const char from = '\\';
|
||||
const char to = '/';
|
||||
}
|
||||
|
||||
public:
|
||||
this(char[] path = "")
|
||||
{
|
||||
// Set up the initial buffer
|
||||
buffer.length = path.length + 50;
|
||||
|
||||
if(path.length)
|
||||
{
|
||||
// Slice the beginning of it and copy the path over
|
||||
sysPath = buffer[0..path.length];
|
||||
sysPath[] = path[];
|
||||
}
|
||||
|
||||
convPath();
|
||||
|
||||
// Make sure the last char in the path is a path separator
|
||||
if(!path.ends([to]))
|
||||
{
|
||||
sysPath = buffer[0..path.length+1];
|
||||
sysPath[$-1] = to;
|
||||
}
|
||||
}
|
||||
|
||||
bool has(char[] file)
|
||||
{
|
||||
char[] pt = getPath(file);
|
||||
return exists(pt) && isfile(pt);
|
||||
}
|
||||
|
||||
bool hasDir(char[] file)
|
||||
{
|
||||
char[] pt = getPath(file);
|
||||
return exists(pt) && isdir(pt);
|
||||
}
|
||||
|
||||
Stream open(char[] file)
|
||||
{ return new BufferedFile(getPath(file)); }
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (monster.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.monster;
|
||||
|
||||
public
|
||||
{
|
||||
// These should contain all you need for normal usage.
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.stack;
|
||||
import monster.vm.vm;
|
||||
import monster.vm.thread;
|
||||
import monster.vm.idlefunction;
|
||||
import monster.vm.arrays;
|
||||
import monster.vm.params;
|
||||
import monster.vm.error;
|
||||
}
|
||||
|
||||
version(LittleEndian) {}
|
||||
else static assert(0, "This library does not yet support big endian systems.");
|
@ -1,192 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (options.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.options;
|
||||
|
||||
/*
|
||||
The purpose of this file is to set compile time options for the
|
||||
Monster library - including compiler, VM and modules. This allows
|
||||
the user to customize the language in various ways to fit each
|
||||
project individually.
|
||||
|
||||
For changes to take effect, you must recompile and reinstall the
|
||||
library.
|
||||
|
||||
If you have suggestions for additional options and ways to customize
|
||||
the language, let us know!
|
||||
*/
|
||||
|
||||
static:
|
||||
const:
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Language options
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// Set to false to make the entire language case insensitive. Affects
|
||||
// all identifier and keyword matching. (Not implemented yet!)
|
||||
bool caseSensitive = true;
|
||||
|
||||
// Include the case-insensitive string (and character) operators =i=,
|
||||
// =I=, !=i= and !=I=.
|
||||
bool ciStringOps = true;
|
||||
|
||||
// Skip lines beginning with a hash character '#'
|
||||
bool skipHashes = true;
|
||||
|
||||
// Do we allow implicit downcasting of classes? Downcasting means
|
||||
// casting from a parent class to a child class. The actual object
|
||||
// type is checked at runtime. In any case you can always downcast
|
||||
// explicitly, using ClassName(obj).
|
||||
bool implicitDowncast = true;
|
||||
|
||||
// Allow implicit conversion from float to int (and similar
|
||||
// conversions). If false, you must use explicit casting,
|
||||
// ie. int(value)
|
||||
bool implicitTruncate = false;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
VM options
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// Whether to add the current working directory to the VFS at
|
||||
// startup. If false, you must add your own directories (using
|
||||
// vm.addPath) or your own VFS implementations, otherwise the library
|
||||
// will not be able to find any script files.
|
||||
bool vmAddCWD = false;
|
||||
|
||||
// Maximum stack size. Prevents stack overflow through infinite
|
||||
// recursion and other bugs.
|
||||
int maxStack = 100;
|
||||
|
||||
// Maximum function stack size
|
||||
int maxFStack = 100;
|
||||
|
||||
// Whether we should limit the number of instructions that execute()
|
||||
// can run at once. Enabling this will prevent infinite loops.
|
||||
bool enableExecLimit = true;
|
||||
|
||||
// Maximum number of instructions to allow in each call to execute()
|
||||
// (if enableExecLimit is true)
|
||||
long execLimit = 10000000;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Debugging options
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// If true, set the log output to standard out. If false, logging is
|
||||
// disabled by default and must be activated (through monster.vm.dbg)
|
||||
// at runtime.
|
||||
bool defaultLogToStdout = false;
|
||||
|
||||
// If true, all function stack operations (pushes and pops) are logged
|
||||
// with dbg.log(), and all logged messages are indented according to
|
||||
// the current fstack level.
|
||||
bool logFStack = true;
|
||||
|
||||
// If true, log when threads are put into the background/forground.
|
||||
bool logThreads = true;
|
||||
|
||||
// The following options control more low-level debugging. Most of
|
||||
// these will enable tracing of various internal function calls and
|
||||
// parameters directly to stdout.
|
||||
|
||||
// Trace the thread system
|
||||
bool traceThreads = false;
|
||||
|
||||
// Trace scope lookups
|
||||
bool traceLookups = false;
|
||||
|
||||
// Trace resolve() calls
|
||||
bool traceResolve = false;
|
||||
|
||||
// Print completed output from the assembler
|
||||
bool printAsmOutput = false;
|
||||
|
||||
// Trace VM opcode execution
|
||||
bool traceVMOps = false;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Optimization options
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// Enable assembler optimizations
|
||||
bool optimizeAsm = true;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Modules
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// Load modules at startup? If false, you can still load modules
|
||||
// manually using monster.modules.all.initAllModules().
|
||||
bool loadModules = true;
|
||||
|
||||
// List of modules to load when initAllModules is called (and at
|
||||
// startup if loadModules is true.)
|
||||
char[] moduleList = "io math timer frames random thread";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Timer module
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// When true, idle function sleep() uses the system clock. When false,
|
||||
// the time is only updated manually when the user calls vm.frame().
|
||||
// The system clock is fine for small applications, but the manual
|
||||
// method is much more optimized. It is highly recommended to use
|
||||
// vm.frame() manually for games and other projects that use a
|
||||
// rendering loop.
|
||||
bool timer_useClock = false;
|
@ -1,192 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (options.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.options;
|
||||
|
||||
/*
|
||||
The purpose of this file is to set compile time options for the
|
||||
Monster library - including compiler, VM and modules. This allows
|
||||
the user to customize the language in various ways to fit each
|
||||
project individually.
|
||||
|
||||
For changes to take effect, you must recompile and reinstall the
|
||||
library.
|
||||
|
||||
If you have suggestions for additional options and ways to customize
|
||||
the language, let us know!
|
||||
*/
|
||||
|
||||
static:
|
||||
const:
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Language options
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// Set to false to make the entire language case insensitive. Affects
|
||||
// all identifier and keyword matching. (Not implemented yet!)
|
||||
bool caseSensitive = true;
|
||||
|
||||
// Include the case-insensitive string (and character) operators =i=,
|
||||
// =I=, !=i= and !=I=.
|
||||
bool ciStringOps = true;
|
||||
|
||||
// Skip lines beginning with a hash character '#'
|
||||
bool skipHashes = true;
|
||||
|
||||
// Do we allow implicit downcasting of classes? Downcasting means
|
||||
// casting from a parent class to a child class. The actual object
|
||||
// type is checked at runtime. In any case you can always downcast
|
||||
// explicitly, using ClassName(obj).
|
||||
bool implicitDowncast = true;
|
||||
|
||||
// Allow implicit conversion from float to int (and similar
|
||||
// conversions). If false, you must use explicit casting,
|
||||
// ie. int(value)
|
||||
bool implicitTruncate = false;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
VM options
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// Whether to add the current working directory to the VFS at
|
||||
// startup. If false, you must add your own directories (using
|
||||
// vm.addPath) or your own VFS implementations, otherwise the library
|
||||
// will not be able to find any script files.
|
||||
bool vmAddCWD = false;
|
||||
|
||||
// Maximum stack size. Prevents stack overflow through infinite
|
||||
// recursion and other bugs.
|
||||
int maxStack = 100;
|
||||
|
||||
// Maximum function stack size
|
||||
int maxFStack = 100;
|
||||
|
||||
// Whether we should limit the number of instructions that execute()
|
||||
// can run at once. Enabling this will prevent infinite loops.
|
||||
bool enableExecLimit = true;
|
||||
|
||||
// Maximum number of instructions to allow in each call to execute()
|
||||
// (if enableExecLimit is true)
|
||||
long execLimit = 10000000;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Debugging options
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// If true, set the log output to standard out. If false, logging is
|
||||
// disabled by default and must be activated (through monster.vm.dbg)
|
||||
// at runtime.
|
||||
bool defaultLogToStdout = false;
|
||||
|
||||
// If true, all function stack operations (pushes and pops) are logged
|
||||
// with dbg.log(), and all logged messages are indented according to
|
||||
// the current fstack level.
|
||||
bool logFStack = true;
|
||||
|
||||
// If true, log when threads are put into the background/forground.
|
||||
bool logThreads = true;
|
||||
|
||||
// The following options control more low-level debugging. Most of
|
||||
// these will enable tracing of various internal function calls and
|
||||
// parameters directly to stdout.
|
||||
|
||||
// Trace the thread system
|
||||
bool traceThreads = false;
|
||||
|
||||
// Trace scope lookups
|
||||
bool traceLookups = false;
|
||||
|
||||
// Trace resolve() calls
|
||||
bool traceResolve = false;
|
||||
|
||||
// Print completed output from the assembler
|
||||
bool printAsmOutput = false;
|
||||
|
||||
// Trace VM opcode execution
|
||||
bool traceVMOps = false;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Optimization options
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// Enable assembler optimizations
|
||||
bool optimizeAsm = true;
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Modules
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// Load modules at startup? If false, you can still load modules
|
||||
// manually using monster.modules.all.initAllModules().
|
||||
bool loadModules = true;
|
||||
|
||||
// List of modules to load when initAllModules is called (and at
|
||||
// startup if loadModules is true.)
|
||||
char[] moduleList = "io math timer frames random thread";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*********************************************************
|
||||
|
||||
|
||||
Timer module
|
||||
|
||||
|
||||
*********************************************************/
|
||||
|
||||
// When true, idle function sleep() uses the system clock. When false,
|
||||
// the time is only updated manually when the user calls vm.frame().
|
||||
// The system clock is fine for small applications, but the manual
|
||||
// method is much more optimized. It is highly recommended to use
|
||||
// vm.frame() manually for games and other projects that use a
|
||||
// rendering loop.
|
||||
bool timer_useClock = false;
|
@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
svn export https://monster-script.svn.sourceforge.net/svnroot/monster-script/trunk/monster/ . --force
|
||||
rm -r minibos vm/c_api.d
|
||||
|
||||
for a in $(find -iname \*.d); do
|
||||
cat "$a" | sed s/monster.minibos./std./g > "$a"_new
|
||||
mv "$a"_new "$a"
|
||||
done
|
||||
|
||||
svn st
|
||||
|
||||
diff options.openmw options.d || $EDITOR options.d
|
||||
mv options.openmw options.openmw_last
|
||||
cp options.d options.openmw
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,147 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (flags.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.util.flags;
|
||||
|
||||
// A generic bitwise flag manager.
|
||||
struct Flags(T)
|
||||
{
|
||||
T flags;
|
||||
|
||||
void set(T t)
|
||||
{ flags |= t; }
|
||||
|
||||
void unset(T t)
|
||||
{ flags ^= flags & t; }
|
||||
|
||||
void set(T t, bool value)
|
||||
{
|
||||
if(value) set(t);
|
||||
else unset(t);
|
||||
assert(has(t) == value);
|
||||
}
|
||||
|
||||
// Does it have all of the bits in the parameter set?
|
||||
bool has(T t)
|
||||
{ return (flags & t) == t; }
|
||||
|
||||
// Does it have any of the bits in the parameter set?
|
||||
bool hasAny(T t)
|
||||
{ return (flags & t) != 0; }
|
||||
|
||||
// For single-bit parameters, has() and hasAny() are identical.
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
Flags!(int) fl;
|
||||
|
||||
assert(fl.flags == 0);
|
||||
|
||||
// Try setting some flags
|
||||
assert(!fl.has(2));
|
||||
fl.set(2);
|
||||
assert(fl.has(2));
|
||||
|
||||
assert(!fl.has(4));
|
||||
fl.set(4, true);
|
||||
assert(fl.has(4));
|
||||
|
||||
assert(fl.flags == 6);
|
||||
|
||||
// Make sure setting them again won't change anything
|
||||
fl.set(2);
|
||||
assert(fl.flags == 6);
|
||||
|
||||
fl.set(4);
|
||||
assert(fl.flags == 6);
|
||||
|
||||
fl.set(2, true);
|
||||
assert(fl.flags == 6);
|
||||
|
||||
fl.set(4, true);
|
||||
assert(fl.flags == 6);
|
||||
|
||||
// Test the has() and hasAny() functions
|
||||
with(fl)
|
||||
{
|
||||
assert( !has(1) ); // 1 is not set
|
||||
assert( has(2) ); // 2 is set
|
||||
assert( !has(3) ); // 2+1 is NOT set, because 1 is missing
|
||||
assert( has(4) ); // 4 is set
|
||||
assert( !has(5) ); // 4+1 is NOT set, 1 is missing
|
||||
assert( has(6) ); // 2+4 is set because both are present
|
||||
assert( !has(7) ); // 1+2+4 not set, 1 missing
|
||||
|
||||
assert( !hasAny(1) );// 1 is not set
|
||||
assert( hasAny(2) ); // 2 is set
|
||||
assert( hasAny(3) ); // 2 is set and part of 3=1+2
|
||||
assert( hasAny(4) ); // 4 is set
|
||||
assert( hasAny(5) ); // 4 is and is part of 5=1+4
|
||||
assert( hasAny(6) ); // 2+4 is set
|
||||
assert( hasAny(7) ); // 2+4 are part of 7
|
||||
assert( !hasAny(8) );
|
||||
assert( !hasAny(9) );
|
||||
}
|
||||
|
||||
// Set a mixed flag
|
||||
fl.set(3);
|
||||
assert(fl.has(3));
|
||||
assert(fl.has(7));
|
||||
assert(fl.flags == 7);
|
||||
|
||||
// Unset a flag
|
||||
fl.unset(2);
|
||||
assert(fl.flags == 5);
|
||||
|
||||
// And again
|
||||
fl.unset(2);
|
||||
assert(fl.flags == 5);
|
||||
|
||||
// Set and unset it with set()
|
||||
fl.set(2,true);
|
||||
assert(fl.flags == 7);
|
||||
fl.set(2,false);
|
||||
assert(fl.flags == 5);
|
||||
|
||||
// Now try an enum
|
||||
enum MF
|
||||
{
|
||||
A = 1,
|
||||
B = 2,
|
||||
AB = 3,
|
||||
C = 4
|
||||
}
|
||||
|
||||
Flags!(MF) mf;
|
||||
mf.set(MF.A);
|
||||
assert(mf.has(MF.A));
|
||||
assert(!mf.has(MF.AB));
|
||||
mf.set(MF.B, true);
|
||||
assert(mf.has(MF.B));
|
||||
assert(mf.has(MF.AB));
|
||||
mf.unset(MF.B);
|
||||
assert(mf.has(MF.A));
|
||||
assert(!mf.has(MF.B));
|
||||
assert(!mf.has(MF.AB));
|
||||
}
|
@ -1,401 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (freelist.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.util.freelist;
|
||||
|
||||
import monster.util.list;
|
||||
import monster.util.growarray;
|
||||
import std.c.stdlib : malloc, free;
|
||||
|
||||
// A freelist for buffers of a given size. The 'size' template
|
||||
// parameter gives the total requested size of the entire struct,
|
||||
// including overhead.
|
||||
struct BufferList(int size)
|
||||
{
|
||||
// Calculate the 'overhead' size of the structs
|
||||
alias void* vp;
|
||||
static const junk = 2*vp.sizeof + int.sizeof;
|
||||
|
||||
static union BuffData(int size)
|
||||
{
|
||||
static assert(size >= 2, "size must be at least 2");
|
||||
|
||||
int[size/int.sizeof] ints;
|
||||
ubyte[size] bytes;
|
||||
}
|
||||
|
||||
// Get the data sizes
|
||||
static const bytes = size - junk;
|
||||
static const ints = bytes / int.sizeof;
|
||||
|
||||
static assert(bytes > 0, "size is too small");
|
||||
|
||||
alias BuffData!(bytes) Value;
|
||||
alias Value* ValuePtr;
|
||||
static assert(Value.sizeof == bytes);
|
||||
|
||||
alias LinkedList!(Value, NoAlloc) List;
|
||||
alias List.Node LNode;
|
||||
|
||||
static struct BufferNode(int size)
|
||||
{
|
||||
LNode data;
|
||||
int index;
|
||||
}
|
||||
|
||||
alias BufferNode!(bytes) Node;
|
||||
alias Node* NodePtr;
|
||||
static assert(Node.sizeof == size);
|
||||
|
||||
private:
|
||||
|
||||
// This is the array that does all the actual allocations. It is
|
||||
// used for quickly looking up indices. GrowArrays are designed to
|
||||
// grow dynamically without reallocation, while still being easily
|
||||
// indexed like a normal array.
|
||||
static GrowArray!(Node) array;
|
||||
|
||||
// The freelist. This is shared between all template instances of
|
||||
// the same size.
|
||||
static List freeList;
|
||||
|
||||
// The nodes belonging to THIS list instance
|
||||
List nodes;
|
||||
|
||||
// Get a new node (move from freelist to node list)
|
||||
ValuePtr getNew()
|
||||
{
|
||||
// Is the freelist empty?
|
||||
if(freeList.length == 0)
|
||||
{
|
||||
// Create a bunch of nodes and shove them into the freelist.
|
||||
const makeSize = 50;
|
||||
|
||||
// Grow the growarray
|
||||
uint len = array.length;
|
||||
array.length = len + makeSize;
|
||||
|
||||
// Loop through the new nodes, number them, and insert them
|
||||
// into freeList
|
||||
for(int i=0; i < makeSize; i++)
|
||||
{
|
||||
NodePtr fn = array.getPtr(i+len);
|
||||
fn.index = i + len;
|
||||
freeList.insertNode(&fn.data);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the first element from the freelist into the node list.
|
||||
auto node = freeList.getHead;
|
||||
freeList.removeNode(node);
|
||||
nodes.insertNodeFirst(node);
|
||||
|
||||
// Return the value pointer. Since the value is always at the
|
||||
// begining of the Node struct, this is the same
|
||||
// pointer.
|
||||
return &node.value;
|
||||
}
|
||||
|
||||
// Move a node back to the freelist ("delete" it)
|
||||
void remove(ValuePtr node)
|
||||
{
|
||||
nodes.removeNode(node);
|
||||
freeList.insertNodeFirst(node);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// Get the node corresponding to an index
|
||||
static ValuePtr getNode(int index)
|
||||
{
|
||||
return &array.getPtr(index).data.value;
|
||||
}
|
||||
|
||||
// Get the index from a node
|
||||
static int getIndex(ValuePtr node)
|
||||
{
|
||||
return ( cast(Node*)node ).index;
|
||||
}
|
||||
|
||||
uint length() { return nodes.length; }
|
||||
static uint totLength() { return array.length; }
|
||||
|
||||
// Move the given node to another list
|
||||
ValuePtr moveTo(ref BufferList fl, ValuePtr node)
|
||||
{
|
||||
nodes.removeNode(node);
|
||||
fl.nodes.insertNodeFirst(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
// Get the first element in the list
|
||||
ValuePtr getHead() { return &nodes.getHead().value; }
|
||||
|
||||
// Loop through the structs in this list
|
||||
int opApply(int delegate(ref Value) dg)
|
||||
{
|
||||
return nodes.opApply(dg);
|
||||
}
|
||||
|
||||
int[] getInt(int isize)
|
||||
{
|
||||
assert(isize <= ints);
|
||||
|
||||
return getNew().ints[0..isize];
|
||||
}
|
||||
|
||||
void freeInt(int[] buf)
|
||||
{
|
||||
assert(buf.length <= ints);
|
||||
remove(cast(ValuePtr)buf.ptr);
|
||||
}
|
||||
|
||||
void* get() { return getNew(); }
|
||||
void free(void* p) { remove(cast(ValuePtr)p); }
|
||||
}
|
||||
|
||||
struct Buffers
|
||||
{
|
||||
static:
|
||||
BufferList!(64) b64;
|
||||
BufferList!(128) b128;
|
||||
BufferList!(256) b256;
|
||||
BufferList!(768) b768;
|
||||
|
||||
/*
|
||||
static this()
|
||||
{
|
||||
writefln("64: ints=%s bytes=%s", b64.ints, b64.bytes);
|
||||
writefln("128: ints=%s bytes=%s", b128.ints, b128.bytes);
|
||||
writefln("256: ints=%s bytes=%s", b256.ints, b256.bytes);
|
||||
writefln("768: ints=%s bytes=%s", b768.ints, b768.bytes);
|
||||
}
|
||||
*/
|
||||
|
||||
int[] getInt(uint size)
|
||||
{
|
||||
if(size <= b64.ints) return b64.getInt(size);
|
||||
else if(size <= b128.ints) return b128.getInt(size);
|
||||
else if(size <= b256.ints) return b256.getInt(size);
|
||||
else if(size <= b768.ints) return b768.getInt(size);
|
||||
// Too large for our lists - just use malloc
|
||||
else
|
||||
{
|
||||
//writefln("WARNING: using malloc for %s ints (%s bytes)", size, size*int.sizeof);
|
||||
return ( cast(int*)malloc(size*int.sizeof) )[0..size];
|
||||
}
|
||||
}
|
||||
|
||||
void free(int[] buf)
|
||||
{
|
||||
uint size = buf.length;
|
||||
if(size <= b64.ints) b64.freeInt(buf);
|
||||
else if(size <= b128.ints) b128.freeInt(buf);
|
||||
else if(size <= b256.ints) b256.freeInt(buf);
|
||||
else if(size <= b768.ints) b768.freeInt(buf);
|
||||
else .free(buf.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/* THIS DOESN'T WORK - because DMD is still stubborn with those
|
||||
template forwarding issues. Instead we'll just reuse the old
|
||||
freelist implementation below.
|
||||
|
||||
// A list that uses a freelist for allocation. It is built on top of
|
||||
// BufferList.
|
||||
struct FreeList(T)
|
||||
{
|
||||
private:
|
||||
// For small sizes, pool together with existing lists.
|
||||
static if(T.sizeof <= 64) static const size = 64;
|
||||
else static if(T.sizeof <= 128) static const size = 128;
|
||||
else static if(T.sizeof <= 256) static const size = 256;
|
||||
// Just use the actual size, rounded up to the nearest 16
|
||||
else static if(T.sizeof % 16 == 0)
|
||||
const size = T.sizeof;
|
||||
else
|
||||
const size = T.sizeof + 16 - (T.sizeof%16);
|
||||
|
||||
alias BufferList!(size) BuffList;
|
||||
BuffList buffer;
|
||||
|
||||
alias BuffList.Value Value;
|
||||
alias BuffList.ValuePtr ValuePtr;
|
||||
|
||||
static assert(T.sizeof <= BuffList.bytes);
|
||||
|
||||
public:
|
||||
|
||||
// Get a new node (move from freelist to node list)
|
||||
T* getNew()
|
||||
{ return cast(T*) buffer.get(); }
|
||||
|
||||
// Move a node back to the freelist ("delete" it)
|
||||
void remove(T* node)
|
||||
{ buffer.free(node); }
|
||||
|
||||
// Get the node corresponding to an index
|
||||
static T* getNode(int index)
|
||||
{ return cast(T*) buffer.getNode(index); }
|
||||
|
||||
// Get the index from a node
|
||||
static int getIndex(T *node)
|
||||
{ return buffer.getIndex(cast(ValuePtr)node); }
|
||||
|
||||
uint length() { return buffer.length(); }
|
||||
static uint totLength() { return buffer.totLength(); }
|
||||
|
||||
// Move the given node to another list
|
||||
T* moveTo(ref FreeList fl, T* node)
|
||||
{
|
||||
auto vp = cast(ValuePtr) node;
|
||||
return cast(T*) buffer.moveTo(fl.buffer, vp);
|
||||
}
|
||||
|
||||
// Get the first element in the list
|
||||
T* getHead() { return cast(T*) buffer.getHead(); }
|
||||
|
||||
// Loop through the structs in this list
|
||||
int opApply(int delegate(ref T) dg)
|
||||
{
|
||||
auto dgc = cast(int delegate(ref Value)) dg;
|
||||
return nodes.opApply(dgc);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// This had to be moved outside FreeList to work around some
|
||||
// irritating DMD template problems. (Can you say Aaargh!)
|
||||
struct __FreeNode(T)
|
||||
{
|
||||
_lstNode!(T) data;
|
||||
int index;
|
||||
}
|
||||
|
||||
// A list that uses a freelist for allocation. Based on
|
||||
// LinkedList. Very basic, only functions that are actually in use in
|
||||
// my own code are implemented.
|
||||
struct FreeList(T)
|
||||
{
|
||||
alias LinkedList!(T, NoAlloc) TList;
|
||||
alias TList.Node TNode;
|
||||
|
||||
private:
|
||||
|
||||
alias __FreeNode!(T) _FreeNode;
|
||||
|
||||
// This is the array that does all the actual allocations. It is
|
||||
// used for quickly looking up indices.
|
||||
static GrowArray!(_FreeNode) array;
|
||||
|
||||
// The freelist. This is shared between all template instances of
|
||||
// the same type, as far as I know. DMD might have some strange
|
||||
// behavior that I am not aware of, but the worst case is that we
|
||||
// end up with multiple freelists, which is not the end of the world
|
||||
// (although slightly inefficient.)
|
||||
static TList freeList;
|
||||
|
||||
// The nodes belonging to THIS list
|
||||
TList nodes;
|
||||
|
||||
public:
|
||||
// Get a new node (move from freelist to node list)
|
||||
T* getNew()
|
||||
{
|
||||
// Is the freelist empty?
|
||||
if(freeList.length == 0)
|
||||
{
|
||||
// Create a bunch of nodes and shove them into the freelist.
|
||||
const makeSize = 100;
|
||||
|
||||
// Grow the growarray
|
||||
uint len = array.length;
|
||||
array.length = len + makeSize;
|
||||
|
||||
// Loop through the new nodes, number them, and insert them
|
||||
// into freeList
|
||||
for(int i=0; i < makeSize; i++)
|
||||
{
|
||||
_FreeNode *fn = array.getPtr(i+len);
|
||||
fn.index = i + len;
|
||||
freeList.insertNode(&fn.data);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the first element from the freelist into the node list.
|
||||
auto node = freeList.getHead;
|
||||
freeList.removeNode(node);
|
||||
nodes.insertNodeFirst(node);
|
||||
|
||||
// Return the value pointer. Since the value is always at the
|
||||
// begining of the Node struct, this is the same
|
||||
// pointer. LinkedList lets us choose if we want to use T* or
|
||||
// Node*.
|
||||
return &node.value;
|
||||
}
|
||||
|
||||
// Get the node corresponding to an index
|
||||
static T* getNode(int index)
|
||||
{
|
||||
return &array.getPtr(index).data.value;
|
||||
}
|
||||
|
||||
// Get the index from a node
|
||||
static int getIndex(T *node)
|
||||
{
|
||||
return ( cast(_FreeNode*)node ).index;
|
||||
}
|
||||
|
||||
// Move a node back to the freelist ("delete" it)
|
||||
void remove(T* node)
|
||||
{
|
||||
nodes.removeNode(node);
|
||||
freeList.insertNodeFirst(node);
|
||||
}
|
||||
|
||||
uint length() { return nodes.length; }
|
||||
static uint totLength() { return array.length; }
|
||||
|
||||
// Move the given node to another list
|
||||
T* moveTo(ref FreeList fl, T* node)
|
||||
{
|
||||
nodes.removeNode(node);
|
||||
fl.nodes.insertNodeFirst(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
// Get the first element in the list
|
||||
T* getHead() { return &nodes.getHead().value; }
|
||||
|
||||
// Get the next element in the list
|
||||
T* getNext(T* nd)
|
||||
{
|
||||
auto node = cast(TNode*)nd;
|
||||
return cast(T*) node.getNext();
|
||||
}
|
||||
|
||||
// Loop through the structs in this list
|
||||
int opApply(int delegate(ref T) dg)
|
||||
{ return nodes.opApply(dg); }
|
||||
}
|
@ -1,312 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (growarray.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.util.growarray;
|
||||
|
||||
// Array that grows without reallocations. It does this by
|
||||
// block-allocation and by stringing several blocks together to work
|
||||
// like one single array. Elements are not guaranteed to be stored
|
||||
// continuously in memory.
|
||||
struct GrowArray(T)
|
||||
{
|
||||
const defSize = 128;
|
||||
|
||||
private:
|
||||
uint listSize = defSize; // Size of new lists
|
||||
uint elements; // Current number of elements
|
||||
uint elemsAlloc; // Elements allocated
|
||||
|
||||
uint begin; // At what element to start counting. Used for
|
||||
// slices.
|
||||
|
||||
T[][] listList;
|
||||
|
||||
// Make sure there is room for at least 'size' elements in total.
|
||||
void alloc(uint size)
|
||||
{
|
||||
// Do nothing if the list is large enough
|
||||
if(size <= elemsAlloc) return;
|
||||
|
||||
// If this is a slice, we must always reallocate when
|
||||
// growing. Implement that later.
|
||||
if(begin) assert(0, "Cannot grow a slice");
|
||||
|
||||
// Number of lists we need
|
||||
uint lists = ((size-1) / listSize) + 1;
|
||||
|
||||
// The number of needed elements should never decrease
|
||||
assert((listSize*lists) >= elemsAlloc);
|
||||
|
||||
// Number of elements we need allocated
|
||||
elemsAlloc = listSize * lists;
|
||||
|
||||
// Make sure the list of lists is large enough
|
||||
if(listList.length < lists)
|
||||
listList.length = lists+30;
|
||||
|
||||
// Allocate the lists we need
|
||||
for(int i=0; i<lists; i++)
|
||||
listList[i].length = listSize;
|
||||
}
|
||||
|
||||
public:
|
||||
uint length() { return elements - begin; }
|
||||
void length(uint newLen)
|
||||
{
|
||||
newLen += begin;
|
||||
|
||||
alloc(newLen);
|
||||
|
||||
elements = newLen;
|
||||
}
|
||||
|
||||
// Initialize - set the current size
|
||||
void initialize(uint size = 0, uint listSize = defSize)
|
||||
{
|
||||
assert(listList.length == 0);
|
||||
this.listSize = listSize;
|
||||
length(size);
|
||||
}
|
||||
|
||||
static GrowArray opCall(uint size = 0, uint listSize = defSize)
|
||||
{
|
||||
GrowArray a;
|
||||
a.initialize(size, listSize);
|
||||
return a;
|
||||
}
|
||||
|
||||
void opCatAssign(T t)
|
||||
{
|
||||
length = length + 1;
|
||||
opIndexAssign(t, length-1);
|
||||
}
|
||||
|
||||
void opCatAssign(T[] list)
|
||||
{
|
||||
uint len = length;
|
||||
length = len + list.length;
|
||||
foreach(int i, ref T t; list)
|
||||
opIndexAssign(t, len+i);
|
||||
}
|
||||
|
||||
T opIndex(int index)
|
||||
{
|
||||
index += begin;
|
||||
assert(index >= begin && index < elements,
|
||||
"GrowArray index out of bounds");
|
||||
|
||||
return listList[index/listSize][index%listSize];
|
||||
}
|
||||
|
||||
T opIndexAssign(T value, int index)
|
||||
{
|
||||
index += begin;
|
||||
assert(index >= begin && index < elements,
|
||||
"GrowArray index out of bounds");
|
||||
|
||||
return (listList[index/listSize][index%listSize] = value);
|
||||
}
|
||||
|
||||
T* getPtr(int index)
|
||||
{
|
||||
index += begin;
|
||||
assert(index >= begin && index < elements,
|
||||
"GrowArray index out of bounds");
|
||||
|
||||
return &listList[index/listSize][index%listSize];
|
||||
}
|
||||
|
||||
GrowArray opSlice(int start, int stop)
|
||||
{
|
||||
assert(start<=stop, "Illegal GrowArray slice");
|
||||
GrowArray ga = *this;
|
||||
ga.begin = begin+start;
|
||||
ga.length = stop-start;
|
||||
return ga;
|
||||
}
|
||||
|
||||
GrowArray opSlice()
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Get a contiguous array copy containg all the elements.
|
||||
T[] arrayCopy()
|
||||
{
|
||||
T[] res = new T[length()];
|
||||
|
||||
// Non-optimized!
|
||||
foreach(i, ref r; res)
|
||||
r = opIndex(i);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int opApply(int delegate(ref int, ref T) dg)
|
||||
{
|
||||
int res;
|
||||
int len = length;
|
||||
int pos = begin%listSize;
|
||||
int list = begin/listSize;
|
||||
for(int i; i<len; i++)
|
||||
{
|
||||
res = dg(i, listList[list][pos++]);
|
||||
if(res) break;
|
||||
|
||||
if(pos == listSize)
|
||||
{
|
||||
list++;
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int opApply(int delegate(ref T) dg)
|
||||
{
|
||||
int res;
|
||||
int len = length;
|
||||
int pos = begin%listSize;
|
||||
int list = begin/listSize;
|
||||
for(int i; i<len; i++)
|
||||
{
|
||||
res = dg(listList[list][pos++]);
|
||||
if(res) break;
|
||||
|
||||
if(pos == listSize)
|
||||
{
|
||||
list++;
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
// Test default initialization
|
||||
GrowArray!(int) arr;
|
||||
assert(arr.begin == 0);
|
||||
assert(arr.length == 0);
|
||||
assert(arr.elements == 0);
|
||||
assert(arr.elemsAlloc == 0);
|
||||
assert(arr.listList.length == 0);
|
||||
assert(arr.listSize == 128);
|
||||
|
||||
// Setting length
|
||||
arr.length = 3;
|
||||
assert(arr.length == 3);
|
||||
assert(arr.elements == 3);
|
||||
assert(arr.elemsAlloc == arr.listSize);
|
||||
assert(arr.listList.length >= 1);
|
||||
|
||||
// Setting and reading elements
|
||||
arr[0] = 1;
|
||||
arr[1] = 2;
|
||||
arr[2] = 3;
|
||||
assert(arr[0] == 1);
|
||||
assert(arr[1] == 2);
|
||||
assert(arr[2] == 3);
|
||||
assert(arr.listList[0][0] == 1);
|
||||
assert(arr.listList[0][1] == 2);
|
||||
assert(arr.listList[0][2] == 3);
|
||||
|
||||
// Test opCatAssign
|
||||
arr ~= 4;
|
||||
assert(arr.length == 4);
|
||||
assert(arr[3] == 4);
|
||||
|
||||
// Foreach
|
||||
int tmp = 0;
|
||||
foreach(int i, int v; arr)
|
||||
{
|
||||
assert(v==i+1);
|
||||
tmp++;
|
||||
}
|
||||
assert(tmp == 4);
|
||||
|
||||
tmp = 1;
|
||||
foreach(int v; arr)
|
||||
assert(v == tmp++);
|
||||
assert(tmp == 5);
|
||||
|
||||
// Slicing the entire array
|
||||
arr = arr[0..4];
|
||||
assert(arr.length == 4);
|
||||
assert(arr[3] == 4);
|
||||
|
||||
// Slicing part of the array
|
||||
auto arrS = arr[1..3];
|
||||
assert(arrS.length == 2);
|
||||
assert(arrS[0] == 2);
|
||||
assert(arrS[1] == 3);
|
||||
arrS[0] = 10;
|
||||
assert(arr[1] == 10);
|
||||
|
||||
// Slicing the slice
|
||||
arrS = arrS[1..2];
|
||||
assert(arrS.length == 1);
|
||||
assert(arrS[0] == 3);
|
||||
|
||||
// Empty slice
|
||||
arrS = arr[3..3];
|
||||
assert(arrS.length == 0);
|
||||
|
||||
// Custom list size, and more than one list
|
||||
auto arr2 = GrowArray!(byte)(3,2);
|
||||
assert(arr2.length == 3);
|
||||
assert(arr2.elements == 3);
|
||||
assert(arr2.listSize == 2);
|
||||
assert(arr2.elemsAlloc == 4);
|
||||
assert(arr2.listList.length >= 2);
|
||||
assert(arr2.listList[0].length == 2);
|
||||
|
||||
assert(arr2[0] == 0);
|
||||
assert(arr2[1] == 0);
|
||||
assert(arr2[2] == 0);
|
||||
|
||||
arr2[1]=2;
|
||||
arr2[2]=4;
|
||||
|
||||
foreach(int i, byte v; arr2)
|
||||
assert(v == 2*i);
|
||||
|
||||
// Check that boundry checking works (in non-release mode.)
|
||||
bool err = false;
|
||||
try{arr2[3];}
|
||||
catch
|
||||
{
|
||||
err = true;
|
||||
}
|
||||
assert(err == true);
|
||||
|
||||
err = false;
|
||||
try{arr2[3] = 0;}
|
||||
catch
|
||||
{
|
||||
err = true;
|
||||
}
|
||||
assert(err == true);
|
||||
}
|
@ -1,641 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (list.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.util.list;
|
||||
|
||||
// Set this to enable some more extensive list checks. These will loop
|
||||
// through the entire list on every insert and remove, so they are
|
||||
// very slow for large lists. But they are very handy bug catchers
|
||||
// when doing a little dirty list hacking.
|
||||
// debug=slowcheck;
|
||||
|
||||
private import std.c.stdlib;
|
||||
private import std.string;
|
||||
|
||||
typedef void GCAlloc;
|
||||
|
||||
alias malloc cmalloc;
|
||||
alias free cfree;
|
||||
|
||||
class LinkedListException : Exception
|
||||
{
|
||||
this(char[] msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Internal structure used by List
|
||||
*/
|
||||
|
||||
align(1)
|
||||
struct _lstNode(Value)
|
||||
{
|
||||
// It is essential that the value is first in the struct. This
|
||||
// allows us to interchange pointers to the value with pointer to
|
||||
// the Node. This is done for convenience - allowing us to use the
|
||||
// value directly instead of using somePtr.value, This also
|
||||
// sidesteps the fact that DMD isn't very good with template forward
|
||||
// references, something that creates a lot of problems if we use
|
||||
// LinkedList.Iterator for everything (trust me on this.)
|
||||
Value value;
|
||||
|
||||
_lstNode* getNext() { return next; }
|
||||
_lstNode* getPrev() { return prev; }
|
||||
|
||||
private:
|
||||
_lstNode* next; // Next node
|
||||
_lstNode* prev; // Previous node
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a doubly linked list. It's not terribly advanced at the
|
||||
* moment, but I don't need any more functionality right now.
|
||||
*
|
||||
* Alloc must have the following members:
|
||||
* void* alloc(uint size)
|
||||
* void free(void*)
|
||||
* bool autoinit; // True if alloc automatically sets memory to zero.
|
||||
*/
|
||||
|
||||
// Example allocator using malloc and free:
|
||||
struct Malloc
|
||||
{
|
||||
static const bool autoinit = false; // malloc does not initialize memory
|
||||
static const bool usefree = true; // We must call free() to release memory
|
||||
static void* alloc(uint size) { return cmalloc(size); }
|
||||
static void free(void* p) { cfree(p); }
|
||||
}
|
||||
|
||||
// A null allocator. Use if you only intend to move nodes into and out
|
||||
// of the list, not to allocate them. Useful for a freelist, for
|
||||
// example.
|
||||
struct NoAlloc
|
||||
{
|
||||
static const bool autoinit = false;
|
||||
static const bool usefree = true;
|
||||
static void *alloc(uint size) { assert(0, "NoAlloc.alloc not allowed"); }
|
||||
static void free(void *p) { assert(0, "NoAlloc.free not allowed"); }
|
||||
}
|
||||
|
||||
struct LinkedList(Value, alias Alloc = GCAlloc)
|
||||
{
|
||||
alias _lstNode!(Value) Node;
|
||||
|
||||
private:
|
||||
|
||||
Node *head; // This is the head of the linked list (first element)
|
||||
Node *tail; // New nodes are inserted here
|
||||
uint totalNum; // Number of elements
|
||||
|
||||
// Determine if the allocator automatically initializes memory
|
||||
static if(is(Alloc == GCAlloc))
|
||||
static const bool autoinit = true;
|
||||
else static if(Alloc.autoinit)
|
||||
static const bool autoinit = true;
|
||||
else
|
||||
static const bool autoinit = false;
|
||||
|
||||
// Determine if we have to manually free memory
|
||||
static if(is(Alloc == GCAlloc))
|
||||
static const bool usefree = false;
|
||||
else static if(Alloc.usefree)
|
||||
static const bool usefree = true;
|
||||
else
|
||||
static const bool usefree = false;
|
||||
|
||||
// Throw an exception
|
||||
void fail(char[] msg)
|
||||
{
|
||||
msg = format("LinkedList!(%s) exception: %s", typeid(Value).toString, msg);
|
||||
throw new LinkedListException(msg);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// Whenever you find a bug that creates an invalid state, put it in
|
||||
// here so we can safeguard against regressions
|
||||
|
||||
invariant()
|
||||
{
|
||||
if(head != null || tail != null || totalNum != 0)
|
||||
{
|
||||
assert(head != null);
|
||||
assert(tail != null);
|
||||
assert(totalNum != 0);
|
||||
|
||||
assert(head.prev == null);
|
||||
assert(tail.next == null);
|
||||
}
|
||||
}
|
||||
|
||||
alias Node* Iterator;
|
||||
|
||||
// Simply reset all pointers and variables, losing any nodes
|
||||
// present.
|
||||
void reset()
|
||||
{
|
||||
head = tail = null;
|
||||
totalNum = 0;
|
||||
}
|
||||
|
||||
// Go through the list and delete all nodes
|
||||
void deleteAll()
|
||||
{
|
||||
// If there is no need to free objects, then deleteAll() is
|
||||
// equivalent to reset().
|
||||
static if(usefree)
|
||||
{
|
||||
// Loop through the list and delete everything
|
||||
Node *p = head;
|
||||
while(p != null)
|
||||
{
|
||||
Node *next = p.next;
|
||||
Alloc.free(p);
|
||||
p = next;
|
||||
}
|
||||
}
|
||||
reset();
|
||||
}
|
||||
|
||||
Iterator getHead() { return head; }
|
||||
Iterator getTail() { return tail; }
|
||||
|
||||
// Check if the given iterator is part of the list
|
||||
bool hasIterator(Node *v)
|
||||
{
|
||||
Node* p = head;
|
||||
|
||||
while(p != null)
|
||||
{
|
||||
if(p == v)
|
||||
{
|
||||
assert(length >= 1);
|
||||
return true;
|
||||
}
|
||||
p = p.next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert a value at the end of the list.
|
||||
alias insert insertLast;
|
||||
Iterator insert(Value v)
|
||||
{
|
||||
Node *p = createNode();
|
||||
p.value = v;
|
||||
return insertNode(p);
|
||||
}
|
||||
// Also allow ~= syntax for this
|
||||
Iterator opCatAssign(Value v) { return insert(v); }
|
||||
|
||||
// Insert an existing node at the end of the list. The insertNode*()
|
||||
// variants along with removeNode() are useful for removing and
|
||||
// reinserting nodes without allocating more memory. This can for
|
||||
// example be used for free lists and similar constructions. In
|
||||
// other words, you can use these to move elements from one list to
|
||||
// another.
|
||||
alias insertNode insertNodeLast;
|
||||
Iterator insertNode(Node *p)
|
||||
in
|
||||
{
|
||||
assert(!hasIterator(p), "inserNode: Node is already in the list");
|
||||
}
|
||||
body
|
||||
{
|
||||
if(tail)
|
||||
{
|
||||
// Insert node at the end of the list
|
||||
assert(head != null);
|
||||
tail.next = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the first element to be inserted
|
||||
assert(head == null);
|
||||
head = p;
|
||||
}
|
||||
p.prev = tail;
|
||||
tail = p;
|
||||
p.next = null;
|
||||
|
||||
totalNum++;
|
||||
|
||||
return p;
|
||||
}
|
||||
// The Value* variants of the node functions work the same way as
|
||||
// their Iterator (Node*) versions. The pointers are the same, they
|
||||
// just need to be recast.
|
||||
Value* insertNode(Value *p)
|
||||
{ return &insertNode( cast(Node*)p ).value; }
|
||||
|
||||
// Beginning of the list
|
||||
Iterator insertFirst(Value v)
|
||||
{
|
||||
Node *p = createNode();
|
||||
p.value = v;
|
||||
return insertNodeFirst(p);
|
||||
}
|
||||
|
||||
Iterator insertNodeFirst(Node *p)
|
||||
in
|
||||
{
|
||||
debug(slowcheck)
|
||||
assert(!hasIterator(p), "inserNodeFirst: Node is already in the list");
|
||||
}
|
||||
body
|
||||
{
|
||||
if(head)
|
||||
{
|
||||
// Insert node at the beginning of the list
|
||||
assert(tail != null);
|
||||
head.prev = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is the first element to be inserted
|
||||
assert(tail == null);
|
||||
tail = p;
|
||||
}
|
||||
p.next = head;
|
||||
head = p;
|
||||
p.prev = null;
|
||||
|
||||
totalNum++;
|
||||
|
||||
return p;
|
||||
}
|
||||
Value* insertNodeFirst(Value *p)
|
||||
{ return &insertNodeFirst( cast(Node*)p ).value; }
|
||||
|
||||
// Insert after a given element
|
||||
Iterator insertAfter(Iterator i, Value v)
|
||||
{
|
||||
Node *p = createNode();
|
||||
p.value = v;
|
||||
return insertNodeAfter(i, p);
|
||||
}
|
||||
|
||||
// Insert p after i
|
||||
Iterator insertNodeAfter(Iterator i, Node *p)
|
||||
in
|
||||
{
|
||||
//debug(slowcheck)
|
||||
{
|
||||
assert(!hasIterator(p), "inserNodeAfter: Node is already in the list");
|
||||
assert(hasIterator(i), "insertNodeAfter(): element i not part of the list");
|
||||
}
|
||||
}
|
||||
body
|
||||
{
|
||||
// If i is the last element, then insertNodeLast already does a
|
||||
// stellar job of inserting
|
||||
if(i == tail)
|
||||
return insertNodeLast(p);
|
||||
|
||||
// Make p point to the right elements
|
||||
p.next = i.next;
|
||||
p.prev = i;
|
||||
|
||||
// Random consistency check
|
||||
assert(i == i.next.prev);
|
||||
|
||||
// Make the right elements point to p
|
||||
i.next = p;
|
||||
assert(p.next != null);
|
||||
p.next.prev = p;
|
||||
|
||||
totalNum++;
|
||||
|
||||
return p;
|
||||
}
|
||||
// Insert p after i
|
||||
Value* insertNodeAfter(Value* p, Value* i)
|
||||
{ return &insertNodeAfter( cast(Node*)p, cast(Node*)i ).value; }
|
||||
|
||||
|
||||
// Insert value v before i
|
||||
Iterator insertBefore(Iterator i, Value v)
|
||||
{
|
||||
Node *p = createNode();
|
||||
p.value = v;
|
||||
return insertNodeBefore(i, p);
|
||||
}
|
||||
|
||||
// Insert p before i
|
||||
Iterator insertNodeBefore(Iterator i, Node *p)
|
||||
in
|
||||
{
|
||||
//debug(slowcheck)
|
||||
{
|
||||
assert(!hasIterator(p), "inserNodeBefore: Node is already in the list");
|
||||
assert(hasIterator(i), "insertBefore(): element not part of the list");
|
||||
}
|
||||
}
|
||||
body
|
||||
{
|
||||
// If i is the first, just insert at the beginning
|
||||
if(i==head) return insertNodeFirst(p);
|
||||
|
||||
// If I mess it up, an assertion failure is easier to debug than
|
||||
// a segfault.
|
||||
assert(i.prev != null);
|
||||
|
||||
// Reuse insertAfter instead of reinventing the wheel
|
||||
return insertNodeAfter(i.prev, p);
|
||||
}
|
||||
// Insert p before i
|
||||
Value* insertNodeBefore(Value* p, Value* i)
|
||||
{ return &insertNodeBefore( cast(Node*)p, cast(Node*)i ).value; }
|
||||
|
||||
|
||||
// Swap position of element a and b
|
||||
void swap(Iterator a, Iterator b)
|
||||
in
|
||||
{
|
||||
//debug(slowcheck)
|
||||
assert(hasIterator(a) && hasIterator(b),
|
||||
"swap(a,b): both elements must be in the list");
|
||||
}
|
||||
body
|
||||
{
|
||||
Iterator tmp;
|
||||
|
||||
// Handle special cases first
|
||||
|
||||
// The same element? Do nothing.
|
||||
if(a==b) return;
|
||||
|
||||
// Are they next to each other?
|
||||
if(b.next == a)
|
||||
{
|
||||
// Swap it so we have a before b, then handle it below.
|
||||
assert(a.prev == b);
|
||||
tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
|
||||
// Point a.prev to b
|
||||
if(a.prev) a.prev.next = b;
|
||||
else
|
||||
{
|
||||
assert(head == a);
|
||||
head = b;
|
||||
}
|
||||
// Point to b.next a
|
||||
if(b.next) b.next.prev = a;
|
||||
else
|
||||
{
|
||||
assert(tail == b);
|
||||
tail = a;
|
||||
}
|
||||
|
||||
// From this point on, if a is next to b it must be handled as a
|
||||
// special case. We have already swapped them above so that a is
|
||||
// before b.
|
||||
if(a.next == b)
|
||||
{
|
||||
assert(b.prev == a);
|
||||
|
||||
// Assign outer pointers
|
||||
b.prev = a.prev;
|
||||
a.next = b.next;
|
||||
|
||||
// Assign inner pointers
|
||||
a.prev = b;
|
||||
b.next = a;
|
||||
return;
|
||||
}
|
||||
|
||||
// If a is NOT next to b, continue the pointer orgy.
|
||||
|
||||
// Point a.next to b
|
||||
if(a.next) a.next.prev = b;
|
||||
else
|
||||
{
|
||||
assert(tail == a);
|
||||
tail = b;
|
||||
}
|
||||
|
||||
if(b.prev) b.prev.next = a;
|
||||
else
|
||||
{
|
||||
assert(head == b);
|
||||
head = a;
|
||||
}
|
||||
|
||||
// Finally, swap a and b's internal pointers
|
||||
tmp = a.next;
|
||||
a.next = b.next;
|
||||
b.next = tmp;
|
||||
|
||||
tmp = a.prev;
|
||||
a.prev = b.prev;
|
||||
b.prev = tmp;
|
||||
}
|
||||
void swap(Value* a, Value* b)
|
||||
{ swap( cast(Node*)a, cast(Node*)b ); }
|
||||
|
||||
// Remove a node from the list and delete it
|
||||
void remove(Iterator p)
|
||||
{
|
||||
removeNode(p);
|
||||
deleteNode(p);
|
||||
}
|
||||
|
||||
// Just remove the node from the list, do not delete it.
|
||||
void removeNode(Iterator p)
|
||||
in
|
||||
{
|
||||
//debug(slowcheck)
|
||||
assert(hasIterator(p), "remove(): element not part of the list");
|
||||
}
|
||||
body
|
||||
{
|
||||
// Remove from the list
|
||||
if(p.next)
|
||||
{
|
||||
p.next.prev = p.prev;
|
||||
|
||||
// Make sure we are NOT tail
|
||||
assert(tail != p);
|
||||
}
|
||||
else // We're the tail
|
||||
{
|
||||
assert(tail == p);
|
||||
tail = p.prev;
|
||||
}
|
||||
|
||||
if(p.prev)
|
||||
{
|
||||
p.prev.next = p.next;
|
||||
|
||||
// We are NOT the head, since we have a previous element
|
||||
assert(head != p);
|
||||
}
|
||||
else // We're head
|
||||
{
|
||||
assert(head == p);
|
||||
head = p.next;
|
||||
}
|
||||
|
||||
totalNum--;
|
||||
}
|
||||
void removeNode(Value *v)
|
||||
{ removeNode( cast(Iterator)v ); }
|
||||
|
||||
// Free a node
|
||||
static private void deleteNode(Node *p)
|
||||
{
|
||||
// For the GC, just release the
|
||||
// pointer into the wild.
|
||||
static if(usefree) Alloc.free(p);
|
||||
}
|
||||
|
||||
// Create a new node and return it's pointer. TODO: Make this
|
||||
// static, and increase totalNum in the insert methods instead.
|
||||
static private Node* createNode()
|
||||
{
|
||||
static if(is(Alloc == GCAlloc))
|
||||
Node *p = new Node;
|
||||
else
|
||||
Node *p = cast(Node*)Alloc.alloc(Node.sizeof);
|
||||
|
||||
// Initialize next pointers
|
||||
static if(!autoinit)
|
||||
{
|
||||
p.next = null;
|
||||
p.prev = null;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
// Loop through the nodes in the order they were inserted
|
||||
int opApply(int delegate(ref Value v) del)
|
||||
{
|
||||
Node *p = head;
|
||||
uint safeGuard = 0;
|
||||
while(p != null)
|
||||
{
|
||||
assert(safeGuard++ < totalNum);
|
||||
int i = del(p.value);
|
||||
if(i) return i;
|
||||
p = p.next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Loop through the nodes in the order they were inserted
|
||||
int opApply(int delegate(ref int ind, ref Value v) del)
|
||||
{
|
||||
Node *p = head;
|
||||
int ind = 0;
|
||||
while(p != null)
|
||||
{
|
||||
assert(ind < totalNum);
|
||||
int i = del(ind, p.value);
|
||||
ind++;
|
||||
if(i) return i;
|
||||
p = p.next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Number of elements
|
||||
uint length() { return totalNum; }
|
||||
|
||||
char[] toString()
|
||||
{
|
||||
char[] res = "[";
|
||||
foreach(int i, Value v; *this)
|
||||
{
|
||||
if(i < totalNum-1) res ~= format(" %s,", v);
|
||||
else res ~= format(" %s ]", v);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
alias LinkedList!(void*, NoAlloc) PointerList;
|
||||
alias PointerList.Node vpNode;
|
||||
alias PointerList.Iterator vpIter;
|
||||
|
||||
/* This test is NOT very complete */
|
||||
unittest
|
||||
{
|
||||
LinkedList!(float) ll;
|
||||
|
||||
assert(ll.length == 0);
|
||||
ll.Iterator it = ll.insert(10.4);
|
||||
assert(ll.length == 1);
|
||||
ll.insert(23);
|
||||
it = ll.insert(6.3);
|
||||
ll.insert(-1000);
|
||||
|
||||
assert(ll.length == 4);
|
||||
|
||||
//foreach(float f; ll) writefln(f);
|
||||
|
||||
ll.remove(it);
|
||||
|
||||
assert(ll.length == 3);
|
||||
|
||||
ll.reset();
|
||||
|
||||
assert(ll.length == 0);
|
||||
|
||||
//foreach(int i, float f; ll) writefln(i, " ", f);
|
||||
}
|
||||
//import std.stdio;
|
||||
|
||||
// Array allocator. TODO: Put this and Malloc in their own place,
|
||||
// extend list to be the same quality as aa.d and make a system out of
|
||||
// it. Make some better unit tests.
|
||||
struct ArrAlloc
|
||||
{
|
||||
ubyte[] data;
|
||||
uint pos;
|
||||
|
||||
void reset() { pos = 0; }
|
||||
|
||||
const bool autoinit = false;
|
||||
const bool usefree = false;
|
||||
|
||||
void* alloc(uint size)
|
||||
{
|
||||
if(pos+size > data.length)
|
||||
data.length = pos+size+30;
|
||||
|
||||
void * ptr = &data[pos];
|
||||
|
||||
pos += size;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void free(void* p) { }
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2004, 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (string.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.util.string;
|
||||
|
||||
import std.utf;
|
||||
import std.string;
|
||||
|
||||
// These functions check whether a string begins or ends with a
|
||||
// certain substring.
|
||||
bool begins(char[] str, char[] start)
|
||||
{
|
||||
if(str.length < start.length ||
|
||||
str[0..start.length] != start) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ends(char[] str, char[] end)
|
||||
{
|
||||
if(str.length < end.length ||
|
||||
str[$-end.length..$] != end) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Case insensitive versions of begins and ends
|
||||
bool iBegins(char[] str, char[] start)
|
||||
{
|
||||
if(str.length < start.length ||
|
||||
icmp(str[0..start.length], start) != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool iEnds(char[] str, char[] end)
|
||||
{
|
||||
if(str.length < end.length ||
|
||||
icmp(str[$-end.length..$], end) != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
char[] nextWord(ref char[] str, char delim = ' ')
|
||||
{
|
||||
int i = find(str, delim);
|
||||
char[] result;
|
||||
|
||||
// No 'delim' found, return the entire string and set remainder to
|
||||
// null.
|
||||
if(i == -1)
|
||||
{
|
||||
result = str;
|
||||
str = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
// A separator was found. Return everything upto 'delim' (index i),
|
||||
// put the remainder of the string (not including the char at [i])
|
||||
// in str.
|
||||
result = str[0..i];
|
||||
str = str[i+1..$];
|
||||
return result;
|
||||
}
|
||||
|
||||
// Strip trailing zeros
|
||||
char[] stripz(char [] s)
|
||||
{
|
||||
foreach(int i, char c; s)
|
||||
if( c == 0 )
|
||||
return s[0..i];
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// Convert a long integer into a string using nice comma
|
||||
// formatting. delim is the delimiter character, size is the number of
|
||||
// digits in each group. See the unittest for examples.
|
||||
char[] comma(long i, char delim=',', int size = 3)
|
||||
{
|
||||
char[] str = toString(i);
|
||||
char[] res;
|
||||
|
||||
if(i<0) str=str[1..$];
|
||||
|
||||
str.reverse;
|
||||
foreach(int j, char c; str)
|
||||
{
|
||||
if(j!=0 && j%size == 0)
|
||||
res = delim ~ res;
|
||||
res = c ~ res;
|
||||
}
|
||||
|
||||
if(i<0) res = "-" ~ res;
|
||||
|
||||
return res;
|
||||
}
|
@ -1,369 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (arrays.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
*/
|
||||
|
||||
module monster.vm.arrays;
|
||||
|
||||
import monster.vm.stack;
|
||||
import monster.util.freelist;
|
||||
import monster.util.flags;
|
||||
import monster.vm.error;
|
||||
import monster.vm.mobject;
|
||||
|
||||
import std.string;
|
||||
import std.uni;
|
||||
import std.stdio;
|
||||
import std.utf;
|
||||
|
||||
// An index to an array. Array indices may be 0, unlike object indices
|
||||
// which span from 1 and upwards, and has 0 as the illegal 'null'
|
||||
// reference. A null array will always refer to an empty array,
|
||||
// because we insert an empty array at the first slot in the index
|
||||
// list.
|
||||
typedef int AIndex;
|
||||
|
||||
// Not all of these are used yet.
|
||||
enum AFlags : int
|
||||
{
|
||||
None = 0x00,
|
||||
Alive = 0x01, // This reference is not deleted
|
||||
Const = 0x02, // Constant data
|
||||
CanCollect = 0x04, // Can be colleted by the GC
|
||||
RefCounted = 0x08, // Is reference counted
|
||||
Marked = 0x10, // Was marked in the last GC sweep
|
||||
Null = 0x20, // Is the null array
|
||||
}
|
||||
|
||||
struct ArrayRef
|
||||
{
|
||||
union
|
||||
{
|
||||
int[] iarr;
|
||||
float[] farr;
|
||||
dchar[] carr;
|
||||
AIndex[] aarr;
|
||||
}
|
||||
Flags!(AFlags) flags;
|
||||
|
||||
uint elemSize; // Size of each element (in ints)
|
||||
|
||||
AIndex getIndex()
|
||||
{
|
||||
return cast(AIndex)( Arrays.ArrayList.getIndex(this) );
|
||||
}
|
||||
|
||||
// Array length, in terms of its element size
|
||||
uint length()
|
||||
{
|
||||
if(isNull) return 0;
|
||||
assert(elemSize != 0, "elemSize not set");
|
||||
assert(iarr.length % elemSize == 0, "array length not divisible by element size");
|
||||
return iarr.length / elemSize;
|
||||
}
|
||||
|
||||
bool isAlive() { return flags.has(AFlags.Alive); }
|
||||
bool isConst() { return flags.has(AFlags.Const); }
|
||||
bool isNull() { return flags.has(AFlags.Null); }
|
||||
}
|
||||
|
||||
Arrays arrays;
|
||||
|
||||
struct Arrays
|
||||
{
|
||||
alias FreeList!(ArrayRef) ArrayList;
|
||||
|
||||
private:
|
||||
ArrayList arrList;
|
||||
|
||||
// Get a new array reference
|
||||
ArrayRef *createArray()
|
||||
{
|
||||
ArrayRef *ar = arrList.getNew();
|
||||
|
||||
assert(!ar.isAlive);
|
||||
|
||||
// Set the "alive" flag
|
||||
ar.flags.set(AFlags.Alive);
|
||||
|
||||
assert(!ar.isNull);
|
||||
|
||||
return ar;
|
||||
}
|
||||
|
||||
// Put a reference back into the freelist
|
||||
void destroyArray(ArrayRef *ar)
|
||||
{
|
||||
assert(ar.isAlive);
|
||||
assert(!ar.isNull);
|
||||
assert(!ar.isConst);
|
||||
ar.flags.unset(AFlags.Alive);
|
||||
arrList.remove(ar);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// Set up this struct
|
||||
void initialize()
|
||||
{
|
||||
// Make sure index zero is valid and is an empty array. Set
|
||||
// more flags later.
|
||||
auto ar = createArray();
|
||||
ar.iarr = null;
|
||||
ar.flags.set(AFlags.Null);
|
||||
|
||||
assert(ar.getIndex == 0);
|
||||
}
|
||||
|
||||
// Get the reference to the empty array
|
||||
ArrayRef *getZero()
|
||||
{
|
||||
return getRef(cast(AIndex)0);
|
||||
}
|
||||
|
||||
ArrayRef *createT(T)(T[] data)
|
||||
{
|
||||
static if(T.sizeof == 4) return create(cast(int[])data, 1);
|
||||
else static if(T.sizeof == 8) return create(cast(int[])data, 2);
|
||||
else static assert(0);
|
||||
}
|
||||
|
||||
alias createT!(int) create;
|
||||
alias createT!(uint) create;
|
||||
alias createT!(long) create;
|
||||
alias createT!(ulong) create;
|
||||
alias createT!(float) create;
|
||||
alias createT!(double) create;
|
||||
alias createT!(dchar) create;
|
||||
alias createT!(AIndex) create;
|
||||
alias createT!(MIndex) create;
|
||||
|
||||
ArrayRef *create(char[] arg)
|
||||
{ return create(toUTF32(arg)); }
|
||||
|
||||
// Generic element size
|
||||
ArrayRef *create(int[] data, int size)
|
||||
{
|
||||
assert(size > 0);
|
||||
|
||||
if(data.length == 0) return getZero();
|
||||
|
||||
ArrayRef *ar = createArray();
|
||||
ar.iarr = data;
|
||||
ar.elemSize = size;
|
||||
|
||||
if(data.length % size != 0)
|
||||
fail("Array length not divisible by element size");
|
||||
|
||||
return ar;
|
||||
}
|
||||
|
||||
ArrayRef *createConst(int[] data, int elem)
|
||||
{
|
||||
ArrayRef *arf = create(data, elem);
|
||||
arf.flags.set(AFlags.Const);
|
||||
return arf;
|
||||
}
|
||||
|
||||
ArrayRef *getRef(AIndex index)
|
||||
{
|
||||
if(index < 0 || index >= getTotalArrays())
|
||||
fail("Invalid array reference: " ~ toString(cast(int)index));
|
||||
|
||||
ArrayRef *arr = ArrayList.getNode(index);
|
||||
|
||||
if(!arr.isAlive)
|
||||
fail("Dead array reference: " ~ toString(cast(int)index));
|
||||
|
||||
assert(arr.getIndex() == index);
|
||||
|
||||
if(index == 0) assert(arr.iarr.length == 0);
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
// Get the number of array references in use
|
||||
int getArrays()
|
||||
{
|
||||
return arrList.length();
|
||||
}
|
||||
|
||||
// Get the total number of Array references ever allocated for the
|
||||
// free list.
|
||||
int getTotalArrays()
|
||||
{
|
||||
return ArrayList.totLength();
|
||||
}
|
||||
}
|
||||
|
||||
// Create a multi-dimensional array of rank 'rank' and innermost data
|
||||
// initialized to 'initval'. The array lengths are popped of the
|
||||
// script stack.
|
||||
void createMultiDimArray(int rank, int init[])
|
||||
{
|
||||
if(rank <= 0 || rank >= 30)
|
||||
fail("Invalid array nesting number " ~ toString(rank));
|
||||
|
||||
assert(init.length > 0);
|
||||
|
||||
int[30] lenbuf;
|
||||
int[] lens = lenbuf[0..rank];
|
||||
int[] data; // All the elements + overhead data
|
||||
ulong totElem = 1; // Total number of elements. Set to 1 and
|
||||
// multiplied with the length later.
|
||||
ulong totSize = 0; // Total size of data to allocate
|
||||
|
||||
int[] currSlice; // Current slice of the data, used by getNext.
|
||||
|
||||
// Get the next 'count' ints of data, in the form of a newly created
|
||||
// ArrayRef.
|
||||
ArrayRef *getNext(int count, int elemSize=1)
|
||||
{
|
||||
assert(count <= currSlice.length);
|
||||
|
||||
int[] res = currSlice[0..count];
|
||||
|
||||
currSlice = currSlice[count..$];
|
||||
|
||||
return arrays.create(res, elemSize);
|
||||
}
|
||||
|
||||
// Get the lengths, and calculate how much data we need. The first
|
||||
// length is the outermost wrapper, the last is the number of
|
||||
// actual elements in the innermost array wrapper.
|
||||
foreach(int i, ref int len; lens)
|
||||
{
|
||||
len = stack.popInt();
|
||||
|
||||
// Do some sanity check on the length. The upper bound set here
|
||||
// is pretty arbitrary, we might enlarge it later.
|
||||
if(len <= 0 || len > 0x100000)
|
||||
fail("Invalid array length " ~ toString(len));
|
||||
|
||||
// We could allow 0-length arrays here, but there's not much
|
||||
// point really.
|
||||
|
||||
// Calculate in the element size in the last element
|
||||
if(i == lens.length-1) len *= init.length;
|
||||
|
||||
// The total data is the cumulative value of totElem through all
|
||||
// iterations. For example, if we have a k*m*n array, we must
|
||||
// have k outer arrays, indexing a total of k*m subarrays,
|
||||
// indexing a total of k*m*n elements. The total data size,
|
||||
// assuming element sizes have been figured in, is
|
||||
// k + k*m + k*m*n.
|
||||
totElem *= len;
|
||||
totSize += totElem;
|
||||
}
|
||||
|
||||
// Allocate all the elements + overhead (data for the lookup arrays)
|
||||
if(totSize)
|
||||
{
|
||||
assert(totElem >= 0 && totElem <= totSize);
|
||||
|
||||
// Let's slap a 10 meg sanity check on the total data size
|
||||
if(totSize > 10*1024*1024)
|
||||
fail("Total array size is too large: " ~ toString(totSize));
|
||||
|
||||
data.length = totSize;
|
||||
|
||||
// Set currSlice to point to the entire data
|
||||
currSlice = data;
|
||||
}
|
||||
|
||||
// Set up inner arrays recursively. This can be optimized heavily
|
||||
// later (removing recursion, moving if-tests out of loops, avoiding
|
||||
// double initialization, and so on.)
|
||||
void setupArray(int lenIndex, ArrayRef *arr)
|
||||
{
|
||||
// Length of arrays at this level
|
||||
int len = lens[lenIndex];
|
||||
|
||||
// Loop through the previous level and create the arrays of this level
|
||||
foreach(ref AIndex ind; arr.aarr)
|
||||
{
|
||||
ArrayRef *narr;
|
||||
if(lenIndex == rank-1)
|
||||
// Remember to set the element size on the inner level
|
||||
narr = getNext(len, init.length);
|
||||
else
|
||||
narr = getNext(len);
|
||||
|
||||
// Store the index or this array in the previous level
|
||||
ind = narr.getIndex();
|
||||
|
||||
// Is this the innermost level?
|
||||
if(lenIndex == rank-1)
|
||||
{
|
||||
// If so, this is an array of elements. Initialize them.
|
||||
if(init.length == 1) narr.iarr[] = init[0];
|
||||
else if(init.length == 2) (cast(long[])narr.iarr)[] = *(cast(long*)init.ptr);
|
||||
else
|
||||
for(int i=0; i<lens[0]; i+=init.length)
|
||||
arr.iarr[i..i+init.length] = init[];
|
||||
}
|
||||
else
|
||||
// If not, set up the indices in this array
|
||||
setupArray(lenIndex+1, narr);
|
||||
}
|
||||
}
|
||||
|
||||
if(rank > 1)
|
||||
{
|
||||
// Create outer array and push it
|
||||
ArrayRef *arr = getNext(lens[0]);
|
||||
stack.pushArray(arr);
|
||||
|
||||
// Recursively set up the sub-arrays
|
||||
setupArray(1, arr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create outer array and push it. Element size has already been
|
||||
// multiplied into the length.
|
||||
ArrayRef *arr = getNext(lens[0], init.length);
|
||||
stack.pushArray(arr);
|
||||
|
||||
// There is only one array level, so this IS the inner
|
||||
// array. Initialize the elements. Optimize for element sizes 1
|
||||
// and 2
|
||||
if(init.length == 1) arr.iarr[] = init[0];
|
||||
else if(init.length == 2) (cast(long[])arr.iarr)[] = *(cast(long*)init.ptr);
|
||||
else
|
||||
for(int i=0; i<lens[0]; i+=init.length)
|
||||
arr.iarr[i..i+init.length] = init[];
|
||||
}
|
||||
|
||||
// Make sure we used all the data!
|
||||
assert(currSlice.length == 0);
|
||||
}
|
||||
|
||||
// There's no phobos function that does unicode case insensitive
|
||||
// string comparison, so let's make one ourselves. This can probably
|
||||
// be optimized. toUniLower is in prinicple an expensive operation,
|
||||
// but not so much if we assume most characters are ascii.
|
||||
bool isUniCaseEqual(dchar[] a, dchar[] b)
|
||||
{
|
||||
if(a.length != b.length) return false;
|
||||
foreach(int i, dchar ch; a)
|
||||
if(ch != b[i] && toUniLower(ch) != toUniLower(b[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (codestream.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.vm.codestream;
|
||||
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
import monster.vm.error;
|
||||
|
||||
// CodeStream is a simple utility structure for reading data
|
||||
// sequentially. It holds a piece of byte compiled code, and keeps
|
||||
// track of the position within the code.
|
||||
struct CodeStream
|
||||
{
|
||||
private:
|
||||
ubyte[] data;
|
||||
int len;
|
||||
ubyte *pos;
|
||||
|
||||
public:
|
||||
|
||||
void setData(ubyte[] data)
|
||||
{
|
||||
this.data = data;
|
||||
len = data.length;
|
||||
pos = data.ptr;
|
||||
}
|
||||
|
||||
// Called when the end of the stream was unexpectedly encountered
|
||||
void eos(char[] func)
|
||||
{
|
||||
char[] res = format("Premature end of input: %s() missing %s byte(s)",
|
||||
func, -len);
|
||||
fail(res);
|
||||
}
|
||||
|
||||
// Jump to given position
|
||||
void jump(int newPos)
|
||||
{
|
||||
if(newPos<0 || newPos>=data.length)
|
||||
fail("Jump out of range");
|
||||
len = data.length - newPos;
|
||||
pos = &data[newPos];
|
||||
}
|
||||
|
||||
// Get the current position
|
||||
int getPos()
|
||||
{
|
||||
return pos-data.ptr;
|
||||
}
|
||||
|
||||
ubyte get()
|
||||
{
|
||||
if(len--) return *(pos++);
|
||||
eos("get");
|
||||
}
|
||||
|
||||
int getInt()
|
||||
{
|
||||
len -= 4;
|
||||
if(len < 0) eos("getInt");
|
||||
int i = *(cast(int*)pos);
|
||||
pos+=4;
|
||||
return i;
|
||||
}
|
||||
|
||||
// Get a slice of the 'size' next ints
|
||||
int[] getIntArray(uint size)
|
||||
{
|
||||
size *=4; // Convert size to bytes
|
||||
len -= size;
|
||||
if(len < 0) eos("getArray");
|
||||
int[] res = cast(int[])pos[0..size];
|
||||
pos += size;
|
||||
return res;
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (dbg.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.vm.dbg;
|
||||
|
||||
import monster.vm.thread;
|
||||
import std.stream;
|
||||
import std.string;
|
||||
import std.cstream;
|
||||
import monster.vm.fstack;
|
||||
import monster.options;
|
||||
|
||||
/*
|
||||
This file is used for runtime debugging, stack tracing, etc.
|
||||
*/
|
||||
|
||||
/*
|
||||
Create a trace for your function that automatically pops itself of
|
||||
the fstack when it goes out of scope. Usage:
|
||||
|
||||
auto scope _t = new MTrace("funcName");
|
||||
|
||||
This is one of the few places where C++ is actually easier to use...
|
||||
|
||||
An alternative way to do the same thing:
|
||||
|
||||
dbg.trace("funcName");
|
||||
scope(exit) dbg.untrace();
|
||||
*/
|
||||
|
||||
scope class MTrace
|
||||
{
|
||||
this(char[] str) { dbg.trace(str); }
|
||||
~this() { dbg.untrace(); }
|
||||
}
|
||||
|
||||
Dbg dbg;
|
||||
|
||||
struct Dbg
|
||||
{
|
||||
Stream dbgOut = null;
|
||||
|
||||
// Add indentation for each function stack level (only works if
|
||||
// logFStack is true in options.d)
|
||||
char[] logLevelString = "| ";
|
||||
|
||||
// Called at startup
|
||||
void init()
|
||||
{
|
||||
static if(defaultLogToStdout)
|
||||
enableStdout();
|
||||
}
|
||||
|
||||
void enableStdout()
|
||||
{
|
||||
dbgOut = dout;
|
||||
}
|
||||
|
||||
void log(char[] msg, Thread *tr = null)
|
||||
{
|
||||
if(dbgOut !is null)
|
||||
{
|
||||
int logLevel = getFStackLevel();
|
||||
int extLevel = getExtLevel();
|
||||
int trdIndex = getThreadIndex(tr);
|
||||
|
||||
char[] str = format("(trd=%s,ext=%s,lev=%s)",
|
||||
trdIndex,
|
||||
extLevel,
|
||||
logLevel);
|
||||
str = format("%-24s", str);
|
||||
dbgOut.writeString(str);
|
||||
|
||||
// If we're logging function stack activity, put in some fancy
|
||||
// indentation as well.
|
||||
static if(logFStack)
|
||||
{
|
||||
for(int i;i<logLevel+extLevel;i++)
|
||||
dbgOut.writeString(logLevelString);
|
||||
}
|
||||
|
||||
dbgOut.writeLine(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Stack tracing functions. These might be used internally in the
|
||||
// engine, so they should not be disabled.
|
||||
|
||||
// Add an external function to the stack
|
||||
void trace(char[] name)
|
||||
{
|
||||
getFStack().pushExt(name);
|
||||
}
|
||||
|
||||
// Pop the last function pushed by trace()
|
||||
void untrace()
|
||||
{
|
||||
auto fs = getFStack();
|
||||
assert(fs.cur !is null && fs.cur.isExternal,
|
||||
"vm.untrace() used on a non-external function stack entry");
|
||||
fs.pop();
|
||||
}
|
||||
|
||||
// Return the current function stack printout
|
||||
char[] getTrace()
|
||||
{ return getFStack().toString(); }
|
||||
|
||||
private:
|
||||
|
||||
int getExtLevel()
|
||||
{
|
||||
return externals.list.length;
|
||||
}
|
||||
|
||||
int getFStackLevel()
|
||||
{
|
||||
if(cthread !is null)
|
||||
return cthread.fstack.list.length;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getThreadIndex(Thread *tr = null)
|
||||
{
|
||||
if(tr !is null)
|
||||
return tr.getIndex();
|
||||
else if(cthread !is null)
|
||||
return cthread.getIndex();
|
||||
else return 0;
|
||||
}
|
||||
|
||||
// Get the active function stack, or the externals stack if no
|
||||
// thread is active.
|
||||
FunctionStack *getFStack()
|
||||
{
|
||||
if(cthread !is null)
|
||||
{
|
||||
assert(!cthread.fstack.isEmpty);
|
||||
return &cthread.fstack;
|
||||
}
|
||||
return &externals;
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (error.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.vm.error;
|
||||
|
||||
import monster.compiler.tokenizer;
|
||||
version(Tango) import tango.core.Exception;
|
||||
import std.string;
|
||||
|
||||
class MonsterException : Exception
|
||||
{
|
||||
this(char[] msg) { super(/*"MonsterException: " ~*/ msg); }
|
||||
}
|
||||
|
||||
// Source file location
|
||||
struct Floc
|
||||
{
|
||||
int line = -1;
|
||||
char[] fname;
|
||||
|
||||
char[] toString() { return format("%s:%s", fname, line); }
|
||||
}
|
||||
|
||||
void fail(char[] msg, Floc loc)
|
||||
{
|
||||
fail(msg, loc.fname, loc.line);
|
||||
}
|
||||
|
||||
void fail(char[] msg, char[] fname, int line)
|
||||
{
|
||||
if(line != -1)
|
||||
fail(format("%s:%s: %s", fname, line, msg));
|
||||
else
|
||||
fail(msg);
|
||||
}
|
||||
|
||||
void fail(char[] msg)
|
||||
{
|
||||
throw new MonsterException(msg);
|
||||
}
|
||||
|
||||
void fail(char[] msg, TokenArray toks)
|
||||
{
|
||||
if(toks.length)
|
||||
fail(msg ~ ", found " ~ toks[0].str, toks[0].loc);
|
||||
else
|
||||
fail(msg ~ ", found end of file");
|
||||
}
|
@ -1,389 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (fstack.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.vm.fstack;
|
||||
|
||||
import monster.vm.codestream;
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.stack;
|
||||
import monster.vm.error;
|
||||
import monster.vm.thread;
|
||||
import monster.vm.dbg;
|
||||
import monster.compiler.states;
|
||||
import monster.compiler.functions;
|
||||
import monster.compiler.linespec;
|
||||
|
||||
import monster.options;
|
||||
import monster.util.freelist;
|
||||
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
|
||||
// "friendly" parameter and stack handling.
|
||||
enum SPType
|
||||
{
|
||||
Function, // A function (script or native)
|
||||
Idle, // Idle function
|
||||
State, // State code
|
||||
External, // An external function represented on the function stack
|
||||
}
|
||||
|
||||
// One entry in the function stack
|
||||
struct StackPoint
|
||||
{
|
||||
CodeStream code; // The byte code handler
|
||||
|
||||
union
|
||||
{
|
||||
Function *func; // What function we are in (if any)
|
||||
State *state; // What state the function belongs to (if any)
|
||||
char[] extName; // Name of external function
|
||||
}
|
||||
|
||||
SPType ftype;
|
||||
|
||||
MonsterObject *obj; // "this"-pointer for the function
|
||||
|
||||
// Get the class owning the function
|
||||
MonsterClass getCls()
|
||||
{
|
||||
assert(isFunc || isState);
|
||||
assert(func !is null);
|
||||
return func.owner;
|
||||
}
|
||||
|
||||
bool isStatic()
|
||||
{ return isFunc() && func.isStatic; }
|
||||
|
||||
bool isFunc()
|
||||
{ return (ftype == SPType.Function) || (ftype == SPType.Idle); }
|
||||
|
||||
bool isState()
|
||||
{ return ftype == SPType.State; }
|
||||
|
||||
bool isIdle()
|
||||
{ return ftype == SPType.Idle; }
|
||||
|
||||
bool isNative()
|
||||
{ return isFunc && func.isNative; }
|
||||
|
||||
bool isNormal()
|
||||
{ return isState || (isFunc && func.isNormal); }
|
||||
|
||||
bool isExternal()
|
||||
{ return ftype == SPType.External; }
|
||||
|
||||
// Get the current source position (file name and line
|
||||
// number). Mostly used for error messages.
|
||||
Floc getFloc()
|
||||
{
|
||||
assert(isFunc || isState);
|
||||
|
||||
Floc fl;
|
||||
fl.fname = getCls().name.loc.fname;
|
||||
|
||||
// Subtract one to make sure we get the last instruction executed,
|
||||
// not the next.
|
||||
int pos = code.getPos() - 1;
|
||||
if(pos < 0) pos = 0;
|
||||
|
||||
if(isFunc)
|
||||
fl.line = findLine(func.lines, pos);
|
||||
else
|
||||
fl.line = findLine(state.lines, pos);
|
||||
|
||||
return fl;
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
{
|
||||
if(isExternal)
|
||||
return "external " ~ extName;
|
||||
|
||||
assert(func !is null);
|
||||
|
||||
char[] type, cls, name;
|
||||
cls = getCls().name.str;
|
||||
if(isState)
|
||||
{
|
||||
type = "state";
|
||||
name = state.name.str;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(isFunc);
|
||||
name = func.name.str;
|
||||
|
||||
if(isIdle) type = "idle";
|
||||
else if(isNormal) type = "script";
|
||||
else if(isNative) type = "native";
|
||||
else assert(0);
|
||||
}
|
||||
|
||||
// Function location and name
|
||||
return format("%s %s.%s", type, cls, name);
|
||||
}
|
||||
}
|
||||
|
||||
alias FreeList!(StackPoint) StackList;
|
||||
alias StackList.TNode *StackNode;
|
||||
|
||||
// External functions that are pushed when there is no active
|
||||
// thread. These will not prevent any future thread from being put in
|
||||
// the background.
|
||||
FunctionStack externals;
|
||||
|
||||
struct FunctionStack
|
||||
{
|
||||
private:
|
||||
|
||||
// Number of native functions on the stack
|
||||
int natives;
|
||||
|
||||
public:
|
||||
|
||||
StackList list;
|
||||
|
||||
// The current entry
|
||||
StackPoint *cur = null;
|
||||
|
||||
// Consistancy checks
|
||||
invariant()
|
||||
{
|
||||
if(cur !is null)
|
||||
{
|
||||
assert(list.length > 0);
|
||||
if(cur.ftype == SPType.State)
|
||||
assert(list.length == 1);
|
||||
}
|
||||
else assert(list.length == 0);
|
||||
}
|
||||
|
||||
// Get the thread associated with this function stack. Depends on
|
||||
// the fact that we are the first member of the Thread, so our
|
||||
// pointers are the same. If this changes, you MUST change this
|
||||
// function as well.
|
||||
Thread *getThread()
|
||||
{
|
||||
return cast(Thread*)this;
|
||||
}
|
||||
|
||||
// Used for debug logging
|
||||
void log(char[] msg)
|
||||
{
|
||||
static if(logFStack)
|
||||
{
|
||||
dbg.log(msg, getThread());
|
||||
}
|
||||
}
|
||||
|
||||
void killAll()
|
||||
{
|
||||
natives = 0;
|
||||
while(list.length)
|
||||
{
|
||||
assert(cur !is null);
|
||||
list.remove(cur);
|
||||
cur = list.getHead();
|
||||
}
|
||||
assert(cur is null);
|
||||
}
|
||||
|
||||
bool hasNatives() { return natives != 0; }
|
||||
|
||||
// Check if the function calling us is a normal function
|
||||
bool isNormal()
|
||||
{
|
||||
return cur !is null && cur.isNormal;
|
||||
}
|
||||
|
||||
// Is the function stack empty?
|
||||
bool isEmpty() { return cur is null; }
|
||||
|
||||
bool isIdle() { return cur !is null && cur.ftype == SPType.Idle; }
|
||||
|
||||
// Are we currently running state code?
|
||||
bool isStateCode() { return list.length == 1 && cur.ftype == SPType.State; }
|
||||
|
||||
// Sets up the next stack point and assigns the given object
|
||||
private void push(MonsterObject *obj)
|
||||
{
|
||||
if(list.length >= maxFStack)
|
||||
fail("Function stack overflow - infinite recursion?");
|
||||
|
||||
assert(cur is null || !cur.isIdle,
|
||||
"Cannot call other script functions from an idle function");
|
||||
|
||||
// Puts a new node at the beginning of the list
|
||||
cur = list.getNew();
|
||||
cur.obj = obj;
|
||||
}
|
||||
|
||||
// Set the stack point up as a function. Allows obj to be null.
|
||||
void push(Function *func, MonsterObject *obj)
|
||||
{
|
||||
push(obj);
|
||||
|
||||
assert(func !is null);
|
||||
cur.ftype = SPType.Function;
|
||||
cur.func = func;
|
||||
|
||||
assert(func.owner !is null);
|
||||
assert(obj is null || func.owner.parentOf(obj.cls));
|
||||
|
||||
// Point the code stream to the byte code, if any.
|
||||
if(func.isNormal)
|
||||
cur.code.setData(func.bcode);
|
||||
else if(func.isNative)
|
||||
natives++;
|
||||
|
||||
assert(!func.isIdle, "don't use fstack.push() on idle functions");
|
||||
|
||||
static if(logFStack)
|
||||
{
|
||||
log("+++ " ~ cur.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Set the stack point up as a state
|
||||
void push(State *st, MonsterObject *obj)
|
||||
{
|
||||
assert(st !is null);
|
||||
assert(isEmpty,
|
||||
"state code can only run at the bottom of the function stack");
|
||||
|
||||
push(obj);
|
||||
cur.ftype = SPType.State;
|
||||
cur.state = st;
|
||||
|
||||
assert(obj !is null);
|
||||
assert(st.owner !is null);
|
||||
assert(st.owner.parentOf(obj.cls));
|
||||
|
||||
// Set up the byte code
|
||||
cur.code.setData(st.bcode);
|
||||
|
||||
static if(logFStack)
|
||||
{
|
||||
log("+++ " ~ cur.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Push an external (non-scripted) function on the function
|
||||
// stack.
|
||||
void pushExt(char[] name)
|
||||
{
|
||||
push(null);
|
||||
natives++;
|
||||
cur.ftype = SPType.External;
|
||||
cur.extName = name;
|
||||
|
||||
static if(logFStack)
|
||||
{
|
||||
log("+++ " ~ cur.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void pushIdle(Function *func, MonsterObject *obj)
|
||||
{
|
||||
push(obj);
|
||||
assert(func !is null);
|
||||
cur.func = func;
|
||||
cur.ftype = SPType.Idle;
|
||||
|
||||
assert(func.owner !is null);
|
||||
assert(obj is null || func.owner.parentOf(obj.cls));
|
||||
assert(func.isIdle, func.name.str ~ "() is not an idle function");
|
||||
|
||||
static if(logFStack)
|
||||
{
|
||||
log("+++ " ~ cur.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Pops one entry of the stack. Checks that the stack level has been
|
||||
// returned to the correct position.
|
||||
void pop()
|
||||
{
|
||||
if(isEmpty)
|
||||
fail("Function stack underflow");
|
||||
|
||||
assert(list.length >= 1);
|
||||
|
||||
if(cur.isNative || cur.isExternal)
|
||||
natives--;
|
||||
assert(natives >= 0);
|
||||
|
||||
static if(logFStack)
|
||||
{
|
||||
log(" -- " ~ cur.toString());
|
||||
}
|
||||
|
||||
// Remove the topmost node from the list, and set cur.
|
||||
assert(cur == list.getHead());
|
||||
list.remove(cur);
|
||||
cur = list.getHead();
|
||||
|
||||
assert(list.length != 0 || cur is null);
|
||||
|
||||
static if(logFStack)
|
||||
{
|
||||
log("");
|
||||
}
|
||||
}
|
||||
|
||||
// Get a stack trace (pretty basic at the moment)
|
||||
char[] toString()
|
||||
{
|
||||
char[] res;
|
||||
|
||||
int i;
|
||||
foreach(ref c; list)
|
||||
{
|
||||
char[] msg;
|
||||
if(i == 0)
|
||||
msg = " (<---- current function)";
|
||||
else if(i == list.length-1)
|
||||
msg = " (<---- first Monster function)";
|
||||
res ~= c.toString ~ msg ~ '\n';
|
||||
i++;
|
||||
}
|
||||
|
||||
// If we're not the externals list, add that one too
|
||||
i = 0;
|
||||
if(this !is &externals)
|
||||
{
|
||||
foreach(ref c; externals.list)
|
||||
{
|
||||
char[] msg;
|
||||
if(i == externals.list.length-1)
|
||||
msg = " (<---- first external function)";
|
||||
res ~= c.toString ~ msg ~ '\n';
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return "Trace:\n" ~ res;
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (gc.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// This file will eventually contain the garbage collector.
|
||||
module monster.vm.gc;
|
@ -1,81 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (idlefunction.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.vm.idlefunction;
|
||||
|
||||
import monster.vm.thread;
|
||||
|
||||
// Idle scheduling actions - returned from initiate()
|
||||
enum IS
|
||||
{
|
||||
Poll, // Poll the hasFinished function regularily
|
||||
Return, // Return to the thread immediately - reenter() is called
|
||||
// first
|
||||
Kill, // Kill the thread
|
||||
Manual, // Handle the scheduling ourselves. We have to schedule
|
||||
// or move the thread to another ThreadList before the
|
||||
// end of initiate()
|
||||
}
|
||||
|
||||
// A callback class for idle functions. A child object of this class
|
||||
// is what you "bind" to idle functions (rather than just a delegate,
|
||||
// like for native functions.) Note that instances are not bound to
|
||||
// specific script objects or threads; one idle function instance may
|
||||
// be called for many objects / threads simultaneously. Any data
|
||||
// specific to this call (such as parameters) must be stored
|
||||
// elsewhere, usually within the Thread.
|
||||
abstract class IdleFunction
|
||||
{
|
||||
// This is called immediately after the idle function is "called"
|
||||
// from M script. It has to handle function parameters (remove them
|
||||
// from the stack), but otherwise does not have to do
|
||||
// anything. Return true if the scheduler should put this idle
|
||||
// function into the condition list, which is usually a good
|
||||
// idea. For functions which never "return", and for event driven
|
||||
// idle functions (which handle their own scheduling), you should
|
||||
// return false.
|
||||
abstract IS initiate(Thread*);
|
||||
|
||||
// This is called whenever the idle function is about to "return" to
|
||||
// state code. It has to push the return value, if any, but
|
||||
// otherwise it can be empty. Note that if the idle function is
|
||||
// aborted (eg. the state is changed), this function is never
|
||||
// called, and abort() is called instead.
|
||||
void reentry(Thread*) {}
|
||||
|
||||
// Called whenever an idle function is aborted, for example by a
|
||||
// state change. No action is usually required.
|
||||
void abort(Thread*) {}
|
||||
|
||||
// The condition that determines if this function has finished. This
|
||||
// is the main method by which the scheduler determines when to
|
||||
// reenter M state code. For example, for an idle function
|
||||
// waitSoundFinish(), this would return false if the sound is still
|
||||
// playing, and true if the sound has finished. If you want a purely
|
||||
// event-driven idle function (rather than polling each frame), you
|
||||
// should return false in initiate and instead reschedule the object
|
||||
// manually when the event occurs. (A nice interface for this has
|
||||
// not been created yet, though.)
|
||||
bool hasFinished(Thread*) { assert(0, "empty hasFinished()"); }
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (init.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// This module makes sure that the library is initialized in all
|
||||
// cases.
|
||||
module monster.vm.init;
|
||||
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.properties;
|
||||
import monster.compiler.scopes;
|
||||
import monster.vm.thread;
|
||||
import monster.vm.stack;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.arrays;
|
||||
import monster.vm.vm;
|
||||
import monster.vm.dbg;
|
||||
|
||||
import monster.modules.all;
|
||||
import monster.options;
|
||||
|
||||
version(Tango)
|
||||
{}
|
||||
else
|
||||
{
|
||||
|
||||
// D runtime stuff
|
||||
version(Posix)
|
||||
{
|
||||
extern (C) void _STI_monitor_staticctor();
|
||||
//extern (C) void _STD_monitor_staticdtor();
|
||||
extern (C) void _STI_critical_init();
|
||||
//extern (C) void _STD_critical_term();
|
||||
}
|
||||
version(Win32)
|
||||
{
|
||||
extern (C) void _minit();
|
||||
}
|
||||
extern (C) void gc_init();
|
||||
//extern (C) void gc_term();
|
||||
extern (C) void _moduleCtor();
|
||||
//extern (C) void _moduleDtor();
|
||||
extern (C) void _moduleUnitTests();
|
||||
|
||||
//extern (C) bool no_catch_exceptions;
|
||||
|
||||
} // end version(Tango) .. else
|
||||
|
||||
|
||||
bool initHasRun = false;
|
||||
|
||||
bool stHasRun = false;
|
||||
|
||||
static this()
|
||||
{
|
||||
assert(!stHasRun);
|
||||
stHasRun = true;
|
||||
|
||||
// While we're here, run the initializer right away if it hasn't run
|
||||
// already.
|
||||
if(!initHasRun)
|
||||
doMonsterInit();
|
||||
}
|
||||
|
||||
void doMonsterInit()
|
||||
{
|
||||
// Prevent recursion
|
||||
assert(!initHasRun, "doMonsterInit should never run more than once");
|
||||
initHasRun = true;
|
||||
|
||||
// First check if D has been initialized.
|
||||
if(!stHasRun)
|
||||
{
|
||||
// Nope. This is normal though if we're running as a C++
|
||||
// library. We have to init the D runtime manually.
|
||||
|
||||
// But this is not supported in Tango at the moment.
|
||||
version(Tango)
|
||||
{
|
||||
assert(0, "tango-compiled C++ library not supported yet");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
version (Posix)
|
||||
{
|
||||
_STI_monitor_staticctor();
|
||||
_STI_critical_init();
|
||||
}
|
||||
|
||||
gc_init();
|
||||
|
||||
version (Win32)
|
||||
{
|
||||
_minit();
|
||||
}
|
||||
|
||||
_moduleCtor();
|
||||
_moduleUnitTests();
|
||||
}
|
||||
}
|
||||
|
||||
assert(stHasRun, "D library initializion failed");
|
||||
|
||||
// Next, initialize the Monster library
|
||||
|
||||
// Initialize the debugger structure
|
||||
dbg.init();
|
||||
|
||||
// Initialize tokenizer
|
||||
initTokenizer();
|
||||
|
||||
// initScope depends on doVMInit setting vm.vfs
|
||||
vm.doVMInit();
|
||||
initScope();
|
||||
|
||||
// The rest of the VM
|
||||
scheduler.init();
|
||||
stack.init();
|
||||
arrays.initialize();
|
||||
|
||||
// Compiles the 'Object' class
|
||||
MonsterClass.initialize();
|
||||
|
||||
// Depends on 'Object'
|
||||
initProperties();
|
||||
|
||||
// Load modules
|
||||
static if(loadModules)
|
||||
initAllModules();
|
||||
}
|
@ -1,281 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (iterators.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
*/
|
||||
|
||||
module monster.vm.iterators;
|
||||
|
||||
import monster.util.freelist;
|
||||
import monster.vm.error;
|
||||
import monster.vm.arrays;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.mobject;
|
||||
import monster.util.flags;
|
||||
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
|
||||
// An iterator index.
|
||||
typedef int IIndex;
|
||||
|
||||
// Flags for iterator structs
|
||||
enum IFlags
|
||||
{
|
||||
None = 0x00,
|
||||
Alive = 0x01, // This reference is not deleted
|
||||
}
|
||||
|
||||
struct IteratorRef
|
||||
{
|
||||
Flags!(IFlags) flags;
|
||||
|
||||
ArrayRef *array;
|
||||
int index; // TODO: Might not be necessary to keep a local copy of this
|
||||
int indexMul; // Index multiplied with element size
|
||||
int elemSize;
|
||||
int *sindex; // Index on the stack
|
||||
int[] sval; // Value on the stack
|
||||
|
||||
bool isReverse, isRef;
|
||||
bool isClass;
|
||||
|
||||
MonsterObject *mo;
|
||||
MonsterClass mc;
|
||||
|
||||
// Array iterators
|
||||
bool firstArray(bool irev, bool iref, int *stk)
|
||||
{
|
||||
isRef = iref;
|
||||
isReverse = irev;
|
||||
isClass = false;
|
||||
|
||||
// Replace the array index on the stack
|
||||
AIndex ai = cast(AIndex)*stk;
|
||||
*stk = cast(int) getIndex();
|
||||
|
||||
// Fetch the array
|
||||
array = arrays.getRef(ai);
|
||||
|
||||
// Cannot use reference values on const arrays
|
||||
if(array.isConst && isRef)
|
||||
// TODO: Try to give line and file in all messages
|
||||
fail("Cannot use 'ref' values with constant arrays");
|
||||
|
||||
// Skip the loop if it's empty
|
||||
if(array.iarr.length == 0) return false;
|
||||
|
||||
assert(array.elemSize > 0);
|
||||
elemSize = array.elemSize;
|
||||
|
||||
// Point to the stack index and value
|
||||
stk -= elemSize;
|
||||
sval = stk[0..elemSize];
|
||||
stk--;
|
||||
sindex = stk;
|
||||
|
||||
// Set up the first element
|
||||
if(isReverse) index = array.length-1;
|
||||
else index = 0;
|
||||
|
||||
indexMul = index * elemSize;
|
||||
|
||||
*sindex = index;
|
||||
sval[] = array.iarr[indexMul..indexMul+elemSize];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Class iterators
|
||||
bool firstClass(MonsterClass mc, int[] stk)
|
||||
{
|
||||
assert(stk.length == 2);
|
||||
isClass = true;
|
||||
|
||||
// Set the iterator index on the stack
|
||||
stk[1] = cast(int) getIndex();
|
||||
|
||||
mo = mc.getFirst();
|
||||
this.mc = mc;
|
||||
|
||||
// Are there any objects?
|
||||
if(mo == null) return false;
|
||||
|
||||
sindex = &stk[0];
|
||||
*sindex = cast(int)mo.getIndex();
|
||||
return true;
|
||||
}
|
||||
|
||||
void storeRef()
|
||||
{
|
||||
assert(!isClass);
|
||||
|
||||
if(isRef)
|
||||
array.iarr[indexMul..indexMul+elemSize] = sval[];
|
||||
else
|
||||
fail("Array iterator update called on non-ref parameter");
|
||||
}
|
||||
|
||||
bool next()
|
||||
{
|
||||
// Handle class iterations seperately
|
||||
if(isClass)
|
||||
{
|
||||
mo = mc.getNext(mo);
|
||||
if(mo == null) return false;
|
||||
|
||||
*sindex = cast(int)mo.getIndex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if(isReverse)
|
||||
{
|
||||
index--;
|
||||
indexMul -= elemSize;
|
||||
if(index == -1) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
index++;
|
||||
indexMul += elemSize;
|
||||
if(index*elemSize == array.iarr.length) return false;
|
||||
}
|
||||
|
||||
assert(indexMul < array.iarr.length);
|
||||
assert(index >= 0 && index < array.length);
|
||||
assert(indexMul == index*elemSize);
|
||||
|
||||
*sindex = index;
|
||||
sval[] = array.iarr[indexMul..indexMul+elemSize];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IIndex getIndex()
|
||||
{
|
||||
return cast(IIndex)( Iterators.IterList.getIndex(this) );
|
||||
}
|
||||
|
||||
bool isAlive() { return flags.has(IFlags.Alive); }
|
||||
}
|
||||
|
||||
Iterators iterators;
|
||||
|
||||
struct Iterators
|
||||
{
|
||||
alias FreeList!(IteratorRef) IterList;
|
||||
|
||||
private:
|
||||
IterList iterList;
|
||||
|
||||
// Get a new iterator reference
|
||||
IteratorRef *createIterator()
|
||||
{
|
||||
IteratorRef *it = iterList.getNew();
|
||||
|
||||
assert(!it.isAlive);
|
||||
|
||||
// Set the "alive" flag
|
||||
it.flags.set(IFlags.Alive);
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
// Put a reference back into the freelist
|
||||
void destroyIterator(IteratorRef *it)
|
||||
{
|
||||
assert(it.isAlive);
|
||||
it.flags.unset(IFlags.Alive);
|
||||
iterList.remove(it);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
bool firstArray(bool irev, bool iref, int *stk)
|
||||
{
|
||||
IteratorRef *it = createIterator();
|
||||
bool res = it.firstArray(irev,iref,stk);
|
||||
|
||||
// Kill the iterator reference if we are done iterating
|
||||
if(!res) destroyIterator(it);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool firstClass(MonsterClass mc, int[] stk)
|
||||
{
|
||||
IteratorRef *it = createIterator();
|
||||
bool res = it.firstClass(mc,stk);
|
||||
|
||||
// Kill the iterator reference if we are done iterating
|
||||
if(!res) destroyIterator(it);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool next(IIndex ind)
|
||||
{
|
||||
IteratorRef *it = getRef(ind);
|
||||
bool res = it.next();
|
||||
|
||||
// Kill the iterator reference if this was the last iteration
|
||||
if(!res) destroyIterator(it);
|
||||
return res;
|
||||
}
|
||||
|
||||
void stop(IIndex ind)
|
||||
{
|
||||
IteratorRef *it = getRef(ind);
|
||||
destroyIterator(it);
|
||||
}
|
||||
|
||||
void update(IIndex ind)
|
||||
{
|
||||
IteratorRef *it = getRef(ind);
|
||||
it.storeRef();
|
||||
}
|
||||
|
||||
IteratorRef *getRef(IIndex index)
|
||||
{
|
||||
if(index < 0 || index >= getTotalIterators())
|
||||
fail("Invalid iterator reference: " ~ toString(cast(int)index));
|
||||
|
||||
IteratorRef *itr = IterList.getNode(index);
|
||||
|
||||
if(!itr.isAlive)
|
||||
fail("Dead iterator reference: " ~ toString(cast(int)index));
|
||||
|
||||
assert(itr.getIndex() == index);
|
||||
|
||||
return itr;
|
||||
}
|
||||
|
||||
// Get the number of iterator references in use
|
||||
int getIterators()
|
||||
{
|
||||
return iterList.length();
|
||||
}
|
||||
|
||||
// Get the total number of Iterator references ever allocated for the
|
||||
// free list.
|
||||
int getTotalIterators()
|
||||
{
|
||||
return IterList.totLength();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,484 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (mobject.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.vm.mobject;
|
||||
|
||||
import monster.vm.thread;
|
||||
import monster.vm.error;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.arrays;
|
||||
import monster.vm.stack;
|
||||
|
||||
import monster.util.freelist;
|
||||
import monster.util.list;
|
||||
|
||||
import monster.compiler.states;
|
||||
import monster.compiler.variables;
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.functions;
|
||||
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
import std.utf;
|
||||
|
||||
// An index to a monster object.
|
||||
typedef int MIndex;
|
||||
|
||||
union SharedType
|
||||
{
|
||||
int i;
|
||||
uint ui;
|
||||
long l;
|
||||
ulong ul;
|
||||
float f;
|
||||
double d;
|
||||
|
||||
void *vptr;
|
||||
Object obj;
|
||||
}
|
||||
|
||||
struct ExtraData
|
||||
{
|
||||
SharedType extra;
|
||||
vpNode node;
|
||||
}
|
||||
|
||||
struct MonsterObject
|
||||
{
|
||||
/*******************************************************
|
||||
* *
|
||||
* Public variables *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
MonsterClass cls;
|
||||
|
||||
// Thread used for running state code. May be null if no code is
|
||||
// running or scheduled.
|
||||
Thread *sthread;
|
||||
|
||||
// The following variables are "tree-indexed". This means that
|
||||
// they're arrays, with one element for each class in the
|
||||
// inheritance hierarchy. The corresponding class tree can be found
|
||||
// in cls.tree.
|
||||
|
||||
// Object data segment.
|
||||
int[][] data;
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Private variables *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
//private:
|
||||
State *state; // Current state, null is the empty state.
|
||||
|
||||
public:
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Functions for object handling *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
// Get the index of this object
|
||||
MIndex getIndex()
|
||||
{
|
||||
return cast(MIndex)( ObjectList.getIndex(this)+1 );
|
||||
}
|
||||
|
||||
// Delete this object. Do not use the object after calling this
|
||||
// function.
|
||||
void deleteSelf()
|
||||
{
|
||||
cls.deleteObject(this);
|
||||
}
|
||||
|
||||
// Create a clone of this object.
|
||||
MonsterObject *clone()
|
||||
{ return cls.createClone(this); }
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Member variable getters / setters *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
// The last two ints of the data segment can be used to store extra
|
||||
// data associated with the object. A typical example is the pointer
|
||||
// to a D/C++ struct or class counterpart to the Monster class.
|
||||
static const exSize = ExtraData.sizeof / int.sizeof;
|
||||
static assert(exSize*4 == ExtraData.sizeof);
|
||||
SharedType *getExtra(int index)
|
||||
{
|
||||
return & (cast(ExtraData*)&data[index][$-exSize]).extra;
|
||||
}
|
||||
SharedType *getExtra(MonsterClass mc)
|
||||
{ return getExtra(cls.upcast(mc)); }
|
||||
|
||||
// This is the work horse for all the set/get functions.
|
||||
T* getPtr(T)(char[] name)
|
||||
{
|
||||
// Find the variable
|
||||
Variable *vb = cls.findVariable(name);
|
||||
assert(vb !is null);
|
||||
|
||||
// Check the type
|
||||
if(!vb.type.isDType(typeid(T)))
|
||||
{
|
||||
char[] request;
|
||||
static if(is(T == dchar)) request = "char"; else
|
||||
static if(is(T == AIndex)) request = "array"; else
|
||||
static if(is(T == MIndex)) request = "object"; else
|
||||
request = typeid(T).toString();
|
||||
|
||||
fail(format("Requested variable %s is not the right type (wanted %s, found %s)",
|
||||
name, request, vb.type.toString()));
|
||||
}
|
||||
|
||||
// Cast the object to the right kind
|
||||
assert(vb.sc.isClass(), "variable must be a class variable");
|
||||
MonsterClass mc = vb.sc.getClass();
|
||||
assert(mc !is null);
|
||||
|
||||
// Return the pointer
|
||||
return cast(T*) getDataInt(mc.treeIndex, vb.number);
|
||||
}
|
||||
T getType(T)(char[] name)
|
||||
{ return *getPtr!(T)(name); }
|
||||
void setType(T)(char[] name, T t)
|
||||
{ *getPtr!(T)(name) = t; }
|
||||
|
||||
alias getPtr!(int) getIntPtr;
|
||||
alias getPtr!(uint) getUintPtr;
|
||||
alias getPtr!(long) getLongPtr;
|
||||
alias getPtr!(ulong) getUlongPtr;
|
||||
alias getPtr!(bool) getBoolPtr;
|
||||
alias getPtr!(float) getFloatPtr;
|
||||
alias getPtr!(double) getDoublePtr;
|
||||
alias getPtr!(dchar) getCharPtr;
|
||||
alias getPtr!(AIndex) getAIndexPtr;
|
||||
alias getPtr!(MIndex) getMIndexPtr;
|
||||
|
||||
alias getType!(int) getInt;
|
||||
alias getType!(uint) getUint;
|
||||
alias getType!(long) getLong;
|
||||
alias getType!(ulong) getUlong;
|
||||
alias getType!(bool) getBool;
|
||||
alias getType!(float) getFloat;
|
||||
alias getType!(double) getDouble;
|
||||
alias getType!(dchar) getChar;
|
||||
alias getType!(AIndex) getAIndex;
|
||||
alias getType!(MIndex) getMIndex;
|
||||
|
||||
alias setType!(int) setInt;
|
||||
alias setType!(uint) setUint;
|
||||
alias setType!(long) setLong;
|
||||
alias setType!(ulong) setUlong;
|
||||
alias setType!(bool) setBool;
|
||||
alias setType!(float) setFloat;
|
||||
alias setType!(double) setDouble;
|
||||
alias setType!(dchar) setChar;
|
||||
alias setType!(AIndex) setAIndex;
|
||||
alias setType!(MIndex) setMIndex;
|
||||
|
||||
MonsterObject *getObject(char[] name)
|
||||
{ return getMObject(getMIndex(name)); }
|
||||
void setObject(char[] name, MonsterObject *obj)
|
||||
{ setMIndex(name, obj.getIndex()); }
|
||||
|
||||
// Array stuff
|
||||
ArrayRef* getArray(char[] name)
|
||||
{ return arrays.getRef(getAIndex(name)); }
|
||||
void setArray(char[] name, ArrayRef *r)
|
||||
{ setAIndex(name,r.getIndex()); }
|
||||
|
||||
char[] getString8(char[] name)
|
||||
{ return toUTF8(getArray(name).carr); }
|
||||
void setString8(char[] name, char[] str)
|
||||
{ setArray(name, arrays.create(toUTF32(str))); }
|
||||
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Lower level member data functions *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
// Get an int from the data segment
|
||||
int *getDataInt(int treeIndex, int pos)
|
||||
{
|
||||
assert(treeIndex >= 0 && treeIndex < data.length,
|
||||
"tree index out of range: " ~ .toString(treeIndex));
|
||||
assert(pos >= 0 && pos<data[treeIndex].length,
|
||||
"data pointer out of range: " ~ .toString(pos));
|
||||
return &data[treeIndex][pos];
|
||||
}
|
||||
|
||||
// Get an array from the data segment
|
||||
int[] getDataArray(int treeIndex, int pos, int len)
|
||||
{
|
||||
assert(len > 0);
|
||||
assert(treeIndex >= 0 && treeIndex < data.length,
|
||||
"tree index out of range: " ~ .toString(treeIndex));
|
||||
assert(pos >= 0 && (pos+len)<=data[treeIndex].length,
|
||||
"data pointer out of range: pos=" ~ .toString(pos) ~
|
||||
", len=" ~.toString(len));
|
||||
return data[treeIndex][pos..pos+len];
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************
|
||||
* *
|
||||
* Calling functions and setting states *
|
||||
* *
|
||||
*******************************************************/
|
||||
|
||||
// Call a named function directly. The function is executed
|
||||
// immediately, and call() returns when the function is
|
||||
// finished. The function is called virtually, so any child class
|
||||
// function that overrides it will take precedence. This is the 'low
|
||||
// level' way to call functions, meaning that you have to handle
|
||||
// parameters and return values on the stack manually. Use the
|
||||
// template functions below for a more high level interface.
|
||||
void call(char[] name)
|
||||
{
|
||||
cls.findFunction(name).call(this);
|
||||
}
|
||||
|
||||
template callT(T)
|
||||
{
|
||||
T callT(A ...)(char[] name, A a)
|
||||
{
|
||||
return cls.findFunction(name).callT!(T)(this, a);
|
||||
}
|
||||
}
|
||||
|
||||
alias callT!(void) callVoid;
|
||||
|
||||
alias callT!(int) callInt;
|
||||
alias callT!(uint) callUint;
|
||||
alias callT!(float) callFloat;
|
||||
alias callT!(double) callDouble;
|
||||
alias callT!(long) callLong;
|
||||
alias callT!(ulong) callUlong;
|
||||
alias callT!(dchar) callChar;
|
||||
alias callT!(MIndex) callMIndex;
|
||||
alias callT!(AIndex) callAIndex;
|
||||
|
||||
// Create a paused thread that's set up to call the given
|
||||
// function. It must be started with Thread.call() or
|
||||
// Thread.restart().
|
||||
Thread *thread(char[] name)
|
||||
{ return thread(cls.findFunction(name)); }
|
||||
Thread *thread(Function *fn)
|
||||
{
|
||||
assert(fn !is null);
|
||||
if(fn.paramSize > 0)
|
||||
fail("thread(): function " ~ fn.name.str ~ " cannot have parameters");
|
||||
|
||||
fn = fn.findVirtual(this);
|
||||
|
||||
Thread *trd = Thread.getNew();
|
||||
|
||||
// Schedule the function to run the next frame
|
||||
trd.pushFunc(fn, this);
|
||||
assert(trd.isPaused);
|
||||
assert(trd.fstack.cur !is null);
|
||||
|
||||
return trd;
|
||||
}
|
||||
|
||||
// Create a thread containing the function and schedule it to start
|
||||
// the next frame
|
||||
Thread *start(char[] name)
|
||||
{ return start(cls.findFunction(name)); }
|
||||
Thread *start(Function *fn)
|
||||
{
|
||||
assert(fn !is null);
|
||||
auto trd = thread(fn);
|
||||
trd.restart();
|
||||
return trd;
|
||||
}
|
||||
|
||||
/* Set state. Invoked by the statement "state = statename;". This
|
||||
function can be called in several situations, with various
|
||||
results:
|
||||
|
||||
+ setState called with current state, no label
|
||||
-> no action is performed
|
||||
|
||||
+ setState called with another state
|
||||
+ setState called with current state + a label
|
||||
-> state is changed normally
|
||||
|
||||
If a state change takes place directly in state code, the code is
|
||||
aborted immediately. If it takes place in a function called from
|
||||
state code, then code flow is allowed to return normally back to
|
||||
the state code level, but is aborted immediately once it reaches
|
||||
state code.
|
||||
|
||||
State changes outside state code will always unschedule any
|
||||
previously scheduled code (such as idle functions, or previous
|
||||
calls to setState.)
|
||||
*/
|
||||
void setState(State *st, StateLabel *label)
|
||||
{
|
||||
// Does the state actually change?
|
||||
if(st !is state)
|
||||
{
|
||||
// Set the state
|
||||
state = st;
|
||||
|
||||
// We must handle state functions and other magic here.
|
||||
}
|
||||
// If no label is specified and we are already in this state, then
|
||||
// don't do anything.
|
||||
else if(label is null) return;
|
||||
|
||||
// Do we already have a thread?
|
||||
if(sthread !is null)
|
||||
{
|
||||
// Check if the thread has gone and died on us while we were
|
||||
// away.
|
||||
if(sthread.isDead)
|
||||
sthread = null;
|
||||
else
|
||||
// Still alive. Stop any execution of the thread
|
||||
sthread.stop();
|
||||
}
|
||||
|
||||
// If we are jumping to anything but the empty state, we will have
|
||||
// to schedule some code.
|
||||
if(st !is null)
|
||||
{
|
||||
// Check that this state is valid
|
||||
assert(st.owner.parentOf(cls), "state '" ~ st.name.str ~
|
||||
"' is not part of class " ~ cls.getName());
|
||||
|
||||
if(label is null)
|
||||
// Use the 'begin:' label, if any. It will be null there's
|
||||
// no begin label.
|
||||
label = st.begin;
|
||||
|
||||
if(label !is null)
|
||||
{
|
||||
// Make sure there's a thread to run in
|
||||
if(sthread is null)
|
||||
sthread = Thread.getNew();
|
||||
|
||||
// Schedule the thread to start at the given state and
|
||||
// label
|
||||
sthread.scheduleState(this, label.offs);
|
||||
assert(sthread.isScheduled);
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing is scheduled, kill the thread
|
||||
if(sthread !is null && !sthread.isScheduled)
|
||||
{
|
||||
assert(sthread.isTransient);
|
||||
sthread.kill();
|
||||
|
||||
// Zero out any pointers to the thread.
|
||||
if(sthread is cthread)
|
||||
cthread = null;
|
||||
sthread = null;
|
||||
}
|
||||
|
||||
assert(sthread is null || sthread.isScheduled);
|
||||
}
|
||||
|
||||
void clearState() { setState(cast(State*)null, null); }
|
||||
|
||||
// Index version of setState - called from bytecode
|
||||
void setState(int st, int label, int clsInd)
|
||||
{
|
||||
if(st == -1)
|
||||
{
|
||||
assert(label == -1);
|
||||
clearState();
|
||||
return;
|
||||
}
|
||||
|
||||
auto cls = cls.upcast(clsInd);
|
||||
|
||||
// TODO: This does not support virtual states yet
|
||||
auto pair = cls.findState(st, label);
|
||||
|
||||
assert(pair.state.index == st);
|
||||
assert(pair.state.owner is cls);
|
||||
|
||||
setState(pair.state, pair.label);
|
||||
}
|
||||
|
||||
// Named version of the above function. An empty string sets the
|
||||
// state to -1 (the empty state.) If no label is given (or given as
|
||||
// ""), this is equivalent to the script command state=name; If a
|
||||
// label is given, it is equivalent to state = name.label;
|
||||
void setState(char[] name, char[] label = "")
|
||||
{
|
||||
if(label == "")
|
||||
{
|
||||
if(name == "") clearState();
|
||||
else setState(cls.findState(name), null);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(name != "", "The empty state cannot contain the label " ~ label);
|
||||
|
||||
auto stl = cls.findState(name, label);
|
||||
setState(stl.state, stl.label);
|
||||
}
|
||||
|
||||
char[] toString()
|
||||
{
|
||||
return cls.toString ~ "#" ~ .toString(cast(int)getIndex());
|
||||
}
|
||||
}
|
||||
|
||||
alias FreeList!(MonsterObject) ObjectList;
|
||||
|
||||
// The freelist used for allocation of objects. This contains all
|
||||
// allocated and in-use objects.
|
||||
ObjectList allObjects;
|
||||
|
||||
// Convert an index to an object pointer
|
||||
MonsterObject *getMObject(MIndex index)
|
||||
{
|
||||
if(index == 0)
|
||||
fail("Null object reference encountered");
|
||||
|
||||
if(index < 0 || index > ObjectList.totLength())
|
||||
fail("Invalid object reference");
|
||||
|
||||
MonsterObject *obj = ObjectList.getNode(index-1);
|
||||
|
||||
if(obj.cls is null)
|
||||
fail("Dead object reference (index " ~ toString(cast(int)index) ~ ")");
|
||||
|
||||
assert(obj.getIndex() == index);
|
||||
|
||||
return obj;
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (params.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.vm.params;
|
||||
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.thread;
|
||||
|
||||
/* This module offers a "friendly" interface for dealing with
|
||||
parameters and return values on the stack. It is meant to be an
|
||||
alternative to manipulating the stack directly when writing native
|
||||
functions.
|
||||
|
||||
NOT FINISHED!
|
||||
*/
|
||||
|
||||
Params params;
|
||||
|
||||
struct Params
|
||||
{
|
||||
static:
|
||||
|
||||
// Get the current object (the 'this' reference for the current
|
||||
// function)
|
||||
MonsterObject *obj()
|
||||
{
|
||||
assert(cthread !is null);
|
||||
assert(cthread.fstack.cur !is null);
|
||||
assert(cthread.fstack.cur.obj !is null);
|
||||
return cthread.fstack.cur.obj;
|
||||
}
|
||||
}
|
@ -1,454 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (stack.d) is part of the Monster script language
|
||||
package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.vm.stack;
|
||||
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
import std.utf;
|
||||
|
||||
import monster.compiler.scopes;
|
||||
import monster.compiler.functions;
|
||||
import monster.options;
|
||||
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.arrays;
|
||||
import monster.vm.error;
|
||||
|
||||
// Stack. There's only one global instance, but threads will make
|
||||
// copies when they need it.
|
||||
CodeStack stack;
|
||||
|
||||
struct FunctionRef
|
||||
{
|
||||
MIndex obj;
|
||||
int fIndex;
|
||||
|
||||
MonsterObject *getObject()
|
||||
{ return getMObject(obj); }
|
||||
|
||||
Function *getFunctionNonVirtual()
|
||||
{ return functionList[fIndex]; }
|
||||
|
||||
Function *getFunction()
|
||||
{
|
||||
auto f = getFunctionNonVirtual();
|
||||
return f.findVirtual(getObject());
|
||||
}
|
||||
|
||||
void set(Function* fn, MonsterObject *mo)
|
||||
{
|
||||
assert(fn !is null);
|
||||
assert(mo !is null);
|
||||
assert(mo.cls.childOf(fn.owner));
|
||||
fIndex = fn.getGIndex();
|
||||
obj = mo.getIndex();
|
||||
}
|
||||
}
|
||||
static assert(FunctionRef.sizeof == 8);
|
||||
|
||||
// A simple stack. All data are in chunks of 4 bytes
|
||||
struct CodeStack
|
||||
{
|
||||
private:
|
||||
int[] data;
|
||||
|
||||
int left, total;
|
||||
int *pos; // Current position
|
||||
|
||||
public:
|
||||
void init()
|
||||
{
|
||||
data.length = maxStack;
|
||||
left = maxStack;
|
||||
total = maxStack;
|
||||
pos = data.ptr;
|
||||
}
|
||||
|
||||
// Get the current position index.
|
||||
int getPos()
|
||||
{
|
||||
return total-left;
|
||||
}
|
||||
|
||||
// Reset the stack level to zero.
|
||||
void reset()
|
||||
{
|
||||
left = total;
|
||||
pos = data.ptr;
|
||||
}
|
||||
|
||||
void pushInt(int i)
|
||||
{
|
||||
left--;
|
||||
if(left<0) overflow("pushInt");
|
||||
*pos = i;
|
||||
pos++;
|
||||
}
|
||||
|
||||
void pushLong(long i)
|
||||
{
|
||||
left -= 2;
|
||||
if(left<0) overflow("pushLong");
|
||||
*(cast(long*)pos) = i;
|
||||
pos+=2;
|
||||
}
|
||||
|
||||
int popInt()
|
||||
{
|
||||
left++;
|
||||
if(left>total) overflow("popInt");
|
||||
pos--;
|
||||
return *pos;
|
||||
}
|
||||
|
||||
long popLong()
|
||||
{
|
||||
left+=2;
|
||||
if(left>total) overflow("popLong");
|
||||
pos-=2;
|
||||
return *(cast(long*)pos);
|
||||
}
|
||||
|
||||
// Get the pointer to an int at the given position backwards from
|
||||
// the current stack pointer. 0 means the first int, ie. the one we
|
||||
// would get if we called popInt. 1 is the next, etc
|
||||
int *getInt(int ptr)
|
||||
{
|
||||
ptr++;
|
||||
if(ptr < 1 || ptr > (total-left) )
|
||||
fail("CodeStack.getInt() pointer out of range");
|
||||
return pos-ptr;
|
||||
}
|
||||
|
||||
// Get the array _beginning_ at ptr
|
||||
int[] getInts(int ptr, int len)
|
||||
{
|
||||
assert(len > 0 && ptr >= len-1);
|
||||
if(left+len-ptr>total) overflow("getInts");
|
||||
return getInt(ptr)[0..len];
|
||||
}
|
||||
|
||||
// Pops the next len ints off the stack and returns them as an
|
||||
// array. The array is ordered as the values were pushed, not as
|
||||
// they would have been popped (ie. this function is like popping
|
||||
// one big value of the stack.) The array is a direct slice of the
|
||||
// stack, so don't store it or use it after pushing other values.
|
||||
int[] popInts(int len)
|
||||
{
|
||||
assert(len > 0);
|
||||
int[] r = getInts(len-1, len);
|
||||
pop(len);
|
||||
assert(r.length == len);
|
||||
return r;
|
||||
}
|
||||
|
||||
void pushInts(int[] arr)
|
||||
{
|
||||
left -= arr.length;
|
||||
if(left<0) overflow("pushInts");
|
||||
pos[0..arr.length] = arr[];
|
||||
pos+=arr.length;
|
||||
}
|
||||
|
||||
// Pushing and poping objects of the stack - will actually push/pop
|
||||
// their index.
|
||||
void pushObject(MonsterObject *mo)
|
||||
{ pushInt(mo.getIndex); }
|
||||
|
||||
MonsterObject *popObject()
|
||||
{ return getMObject(cast(MIndex)popInt()); }
|
||||
|
||||
MonsterObject *peekObject()
|
||||
{ return getMObject(cast(MIndex)peekInt()); }
|
||||
|
||||
// Push arrays of objects. TODO: These do memory allocation, and I'm
|
||||
// not sure that belongs here. I will look into it later.
|
||||
void pushObjects(MonsterObject *objs[])
|
||||
{
|
||||
int[] indices;
|
||||
indices.length = objs.length;
|
||||
|
||||
foreach(i, mo; objs)
|
||||
indices[i] = mo.getIndex();
|
||||
|
||||
pushIArray(indices);
|
||||
}
|
||||
|
||||
MonsterObject*[] popObjects()
|
||||
{
|
||||
MIndex[] indices = cast(MIndex[]) popIArray();
|
||||
MonsterObject* objs[];
|
||||
|
||||
objs.length = indices.length;
|
||||
foreach(i, ind; indices)
|
||||
objs[i] = getMObject(ind);
|
||||
|
||||
return objs;
|
||||
}
|
||||
|
||||
// Push and pop array references.
|
||||
void pushArray(ArrayRef *ar)
|
||||
{ pushInt(ar.getIndex); }
|
||||
ArrayRef *popArray()
|
||||
{ return arrays.getRef(cast(AIndex)popInt()); }
|
||||
ArrayRef *getArray(int i)
|
||||
{ return arrays.getRef(cast(AIndex)*getInt(i)); }
|
||||
ArrayRef *peekArray()
|
||||
{ return getArray(0); }
|
||||
|
||||
// More easy versions. Note that pushArray() will create a new array
|
||||
// reference each time it is called! Only use it if this is what you
|
||||
// want.
|
||||
void pushCArray(dchar[] str) { pushArray(arrays.create(str)); }
|
||||
void pushIArray(int[] str) { pushArray(arrays.create(str)); }
|
||||
void pushUArray(uint[] str) { pushArray(arrays.create(str)); }
|
||||
void pushLArray(long[] str){ pushArray(arrays.create(str)); }
|
||||
void pushULArray(ulong[] str) { pushArray(arrays.create(str)); }
|
||||
void pushFArray(float[] str) { pushArray(arrays.create(str)); }
|
||||
void pushDArray(double[] str) { pushArray(arrays.create(str)); }
|
||||
void pushAArray(AIndex[] str) { pushArray(arrays.create(str)); }
|
||||
void pushMArray(AIndex[] str) { pushArray(arrays.create(str)); }
|
||||
|
||||
alias pushCArray pushArray, pushString;
|
||||
alias pushIArray pushArray;
|
||||
alias pushFArray pushArray;
|
||||
alias pushAArray pushArray;
|
||||
alias pushString8 pushArray, pushString;
|
||||
|
||||
dchar[] popCArray() { return popArray().carr; }
|
||||
int[] popIArray() { return popArray().iarr; }
|
||||
float[] popFArray() { return popArray().farr; }
|
||||
AIndex[] popAArray() { return popArray().aarr; }
|
||||
alias popCArray popString;
|
||||
|
||||
void pushString8(char[] str)
|
||||
{ pushArray(toUTF32(str)); }
|
||||
char[] popString8()
|
||||
{ return toUTF8(popString()); }
|
||||
char[] peekString8()
|
||||
{ return toUTF8(peekArray().carr); }
|
||||
|
||||
// For multibyte arrays
|
||||
void pushArray(int[] str, int size)
|
||||
{ pushArray(arrays.create(str, size)); }
|
||||
|
||||
// Various convenient conversion templates. These will be inlined,
|
||||
// so don't worry :) The *4() functions are for types that are 4
|
||||
// bytes long.
|
||||
void push4(T)(T var)
|
||||
{
|
||||
static assert(T.sizeof == 4);
|
||||
pushInt(*(cast(int*)&var));
|
||||
}
|
||||
T pop4(T)()
|
||||
{
|
||||
static assert(T.sizeof == 4);
|
||||
int i = popInt();
|
||||
return *(cast(T*)&i);
|
||||
}
|
||||
// Gets a pointer to a given stack value. Counts from the head - 0
|
||||
// is the first int, 1 is the second, etc. Note that it counts in
|
||||
// ints (four bytes) no matter what the type T is - this is by
|
||||
// design.
|
||||
T* get4(T)(int ptr) { return cast(T*)getInt(ptr); }
|
||||
|
||||
// Returns the first value on the stack without poping it
|
||||
T peek4(T)() { return *(cast(T*)getInt(0)); }
|
||||
|
||||
// 64 bit version
|
||||
void push8(T)(T var)
|
||||
{
|
||||
static assert(T.sizeof == 8);
|
||||
pushLong(*(cast(long*)&var));
|
||||
}
|
||||
T pop8(T)()
|
||||
{
|
||||
static assert(T.sizeof == 8);
|
||||
long l = popLong();
|
||||
return *(cast(T*)&l);
|
||||
}
|
||||
|
||||
// Bools are 1 byte in D
|
||||
void pushBool(bool b)
|
||||
{
|
||||
if(b) pushInt(1);
|
||||
else pushInt(0);
|
||||
}
|
||||
bool popBool() { return popInt() != 0; }
|
||||
alias get4!(bool) getBool;
|
||||
|
||||
// Template conversions
|
||||
|
||||
alias push4!(MIndex) pushMIndex;
|
||||
alias pop4!(MIndex) popMIndex;
|
||||
alias get4!(MIndex) getMIndex;
|
||||
alias peek4!(MIndex) peekMIndex;
|
||||
|
||||
alias push4!(AIndex) pushAIndex;
|
||||
alias pop4!(AIndex) popAIndex;
|
||||
alias get4!(AIndex) getAIndex;
|
||||
alias peek4!(AIndex) peekAIndex;
|
||||
|
||||
alias peek4!(int) peekInt;
|
||||
|
||||
alias push4!(uint) pushUint;
|
||||
alias pop4!(uint) popUint;
|
||||
alias get4!(uint) getUint;
|
||||
alias peek4!(uint) peekUint;
|
||||
|
||||
alias get4!(long) getLong;
|
||||
alias peek4!(long) peekLong;
|
||||
|
||||
alias push8!(ulong) pushUlong;
|
||||
alias pop8!(ulong) popUlong;
|
||||
alias get4!(ulong) getUlong;
|
||||
alias peek4!(ulong) peekUlong;
|
||||
|
||||
alias push4!(float) pushFloat;
|
||||
alias pop4!(float) popFloat;
|
||||
alias get4!(float) getFloat;
|
||||
alias peek4!(float) peekFloat;
|
||||
|
||||
alias push8!(double) pushDouble;
|
||||
alias pop8!(double) popDouble;
|
||||
alias get4!(double) getDouble;
|
||||
alias peek4!(double) peekDouble;
|
||||
|
||||
alias push4!(dchar) pushChar;
|
||||
alias pop4!(dchar) popChar;
|
||||
alias get4!(dchar) getChar;
|
||||
alias peek4!(dchar) peekDchar;
|
||||
|
||||
alias push8!(FunctionRef) pushFuncRef;
|
||||
alias pop8!(FunctionRef) popFuncRef;
|
||||
alias get4!(FunctionRef) getFuncRef;
|
||||
alias peek4!(FunctionRef) peekFuncRef;
|
||||
|
||||
void pushFuncRef(Function *fn, MonsterObject *obj)
|
||||
{
|
||||
FunctionRef f;
|
||||
f.set(fn,obj);
|
||||
pushFuncRef(f);
|
||||
}
|
||||
|
||||
void pushFail(T)(T t)
|
||||
{
|
||||
static assert(0, "pushType not yet implemented for " ~ T.stringof);
|
||||
}
|
||||
|
||||
T popFail(T)()
|
||||
{
|
||||
static assert(0, "popType not yet implemented for " ~ T.stringof);
|
||||
}
|
||||
|
||||
// Generic push template
|
||||
template pushType(T)
|
||||
{
|
||||
static if(is(T == MIndex) || is(T == AIndex) ||
|
||||
is(T == int) || is(T == uint) || is(T == float))
|
||||
alias push4!(T) pushType;
|
||||
|
||||
else static if(is(T == long) || is(T == ulong) ||
|
||||
is(T == double) || is(T == dchar))
|
||||
alias push8!(T) pushType;
|
||||
|
||||
else
|
||||
alias pushFail!(T) pushType;
|
||||
}
|
||||
|
||||
// Ditto for pop
|
||||
template popType(T)
|
||||
{
|
||||
static if(is(T == MIndex) || is(T == AIndex) ||
|
||||
is(T == int) || is(T == uint) || is(T == float))
|
||||
alias pop4!(T) popType;
|
||||
|
||||
else static if(is(T == long) || is(T == ulong) ||
|
||||
is(T == double) || is(T == dchar))
|
||||
alias pop8!(T) popType;
|
||||
|
||||
else
|
||||
alias popFail!(T) popType;
|
||||
}
|
||||
|
||||
// Pop off and ignore a given amount of values
|
||||
void pop(int num)
|
||||
{
|
||||
left += num;
|
||||
if(left>total) overflow("pop1");
|
||||
pos -= num;
|
||||
}
|
||||
|
||||
// Pop off and ignore given values, but remember the top
|
||||
// values. Equivalent to popping of (and storing) 'keep' ints, then
|
||||
// poping away 'num' ints, and finally pushing the kept ints
|
||||
// back. The final stack imprint is -num.
|
||||
void pop(uint num, uint keep)
|
||||
{
|
||||
assert(keep>0);
|
||||
assert(num>0);
|
||||
|
||||
left += num;
|
||||
|
||||
// We move the stack pointer back num values, but we access as far
|
||||
// back as num+keep values, so we need to check that we are still
|
||||
// within the stack.
|
||||
if((left+keep)>total) overflow("pop2");
|
||||
|
||||
int *from = pos-keep; // Where to get the 'keep' values from
|
||||
int *to = from-num; // Where they end up
|
||||
pos -= num; // Where the final stack pointer should be
|
||||
|
||||
assert(to < from);
|
||||
|
||||
// Copy the values
|
||||
for(; keep>0; keep--)
|
||||
*(to++) = *(from++);
|
||||
}
|
||||
|
||||
void debugPrint()
|
||||
{
|
||||
writefln("Stack:");
|
||||
foreach(int i, int val; data[0..total-left])
|
||||
writefln("%s: %s", i, val);
|
||||
writefln();
|
||||
}
|
||||
|
||||
private:
|
||||
void overflow(char[] func)
|
||||
{
|
||||
char[] res;
|
||||
if(left<0)
|
||||
res = format("Stack overflow by %s ints in CodeStack.%s()",
|
||||
-left, func);
|
||||
else if(left>total)
|
||||
res = format("Stack underflow by %s ints in CodeStack.%s()",
|
||||
left-total, func);
|
||||
else res = format("Internal error in CodeStack.%s(), left=%s, total=%s",
|
||||
func, left, total);
|
||||
fail(res);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,334 +0,0 @@
|
||||
/*
|
||||
Monster - an advanced game scripting language
|
||||
Copyright (C) 2007-2009 Nicolay Korslund
|
||||
Email: <korslund@gmail.com>
|
||||
WWW: http://monster.snaptoad.com/
|
||||
|
||||
This file (vm.d) is part of the Monster script language package.
|
||||
|
||||
Monster is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
module monster.vm.vm;
|
||||
|
||||
import monster.vm.error;
|
||||
import monster.vm.thread;
|
||||
import monster.vm.mclass;
|
||||
import monster.vm.mobject;
|
||||
import monster.vm.init;
|
||||
|
||||
import monster.compiler.tokenizer;
|
||||
import monster.compiler.linespec;
|
||||
import monster.compiler.functions;
|
||||
import monster.compiler.assembler;
|
||||
import monster.compiler.scopes;
|
||||
|
||||
import monster.modules.timer;
|
||||
import monster.modules.frames;
|
||||
import monster.modules.vfs;
|
||||
import monster.options;
|
||||
|
||||
import std.stream;
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
import std.utf;
|
||||
import std.format;
|
||||
import monster.util.string;
|
||||
|
||||
VM vm;
|
||||
|
||||
struct VM
|
||||
{
|
||||
// Run a script file in the context of the object obj. If no object
|
||||
// is given, an instance of an empty class is used.
|
||||
Thread *run(char[] file, MonsterObject *obj = null)
|
||||
{
|
||||
init();
|
||||
|
||||
Thread *trd;
|
||||
auto func = new Function;
|
||||
if(obj !is null)
|
||||
{
|
||||
*func = Function(file, obj.cls);
|
||||
trd = func.call(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
*func = Function(file);
|
||||
trd = func.call();
|
||||
}
|
||||
return trd;
|
||||
}
|
||||
|
||||
void frame(float time = 0)
|
||||
{
|
||||
static if(!timer_useClock)
|
||||
{
|
||||
if(time != 0)
|
||||
idleTime.add(time);
|
||||
}
|
||||
|
||||
updateFrames(time);
|
||||
|
||||
scheduler.doFrame();
|
||||
}
|
||||
|
||||
// Load a class based on class name, file name, or both.
|
||||
MonsterClass load(char[] nam1, char[] nam2 = "")
|
||||
{ return doLoad(nam1, nam2, true, true); }
|
||||
|
||||
// Case insensitive with regards to the given class name
|
||||
MonsterClass loadCI(char[] nam1, char[] nam2 = "")
|
||||
{ return doLoad(nam1, nam2, false, true); }
|
||||
|
||||
// Does not fail if the class is not found, just returns null. It
|
||||
// will still fail if the class exists and contains errors.
|
||||
MonsterClass loadNoFail(char[] nam1, char[] nam2 = "")
|
||||
{ return doLoad(nam1, nam2, true, false); }
|
||||
|
||||
// Load a class from a stream. The filename parameter is only used
|
||||
// for error messages.
|
||||
MonsterClass load(Stream s, char name[] = "", int bom=-1)
|
||||
{
|
||||
init();
|
||||
|
||||
assert(s !is null, "Cannot load from null stream");
|
||||
auto mc = new MonsterClass(global);
|
||||
mc.parse(s, name, bom);
|
||||
return mc;
|
||||
}
|
||||
|
||||
// Load a class from a token array. The filename parameter is only
|
||||
// used for error messages.
|
||||
MonsterClass load(ref TokenArray toks, char[] name = "")
|
||||
{
|
||||
init();
|
||||
|
||||
auto mc = new MonsterClass(global);
|
||||
mc.parse(toks, name);
|
||||
return mc;
|
||||
}
|
||||
|
||||
// Load a class from a string containing the script. The filename
|
||||
// parameter is only used for error messages.
|
||||
MonsterClass loadString(char[] str, char[] name="")
|
||||
{
|
||||
init();
|
||||
|
||||
assert(str != "", "Cannot load empty string");
|
||||
auto ms = new MemoryStream(str);
|
||||
if(name == "") name = "(string)";
|
||||
|
||||
return load(ms, name);
|
||||
}
|
||||
|
||||
void addPath(char[] path)
|
||||
{
|
||||
init();
|
||||
addVFS(new FileVFS(path));
|
||||
}
|
||||
|
||||
void addVFS(VFS fs) { init(); vfs.add(fs); }
|
||||
void addVFSFirst(VFS fs) { init(); vfs.addFirst(fs); }
|
||||
|
||||
void init()
|
||||
{
|
||||
if(!initHasRun)
|
||||
doMonsterInit();
|
||||
}
|
||||
|
||||
// This is called from init(), you don't have to call it yourself.
|
||||
void doVMInit()
|
||||
{
|
||||
assert(vfs is null);
|
||||
vfs = new ListVFS;
|
||||
|
||||
static if(vmAddCWD) addPath("./");
|
||||
}
|
||||
|
||||
ListVFS vfs;
|
||||
|
||||
private:
|
||||
|
||||
// Load file based on file name, class name, or both. The order of
|
||||
// the strings doesn't matter, and name2 can be empty. useCase
|
||||
// determines if we require a case sensitive match between the given
|
||||
// class name and the loaded name. If doThrow is true, we throw an
|
||||
// error if the class was not found, otherwise we just return null.
|
||||
MonsterClass doLoad(char[] name1, char[] name2, bool useCase, bool doThrow)
|
||||
{
|
||||
init();
|
||||
|
||||
char[] fname, cname;
|
||||
MonsterClass mc;
|
||||
|
||||
PackageScope pack = global;
|
||||
assert(pack !is null);
|
||||
|
||||
if(name1 == "")
|
||||
fail("Cannot give empty first parameter to load()");
|
||||
|
||||
if(name1.iEnds(".mn"))
|
||||
{
|
||||
fname = name1;
|
||||
cname = name2;
|
||||
}
|
||||
else
|
||||
{
|
||||
fname = name2;
|
||||
cname = name1;
|
||||
}
|
||||
|
||||
if(cname.iEnds(".mn"))
|
||||
fail("load() recieved two filenames: " ~ fname ~ " and " ~ cname);
|
||||
|
||||
// The filename must either be empty, or end with .mn
|
||||
if(fname != "" && !fname.iEnds(".mn"))
|
||||
fail("Neither " ~ name1 ~ " nor " ~ name2 ~
|
||||
" is a valid script filename.");
|
||||
|
||||
// Remember if cname was originally set
|
||||
bool cNameSet = (cname != "");
|
||||
|
||||
// Was a filename given?
|
||||
if(fname != "")
|
||||
{
|
||||
// Derive the class and package names from the given file name.
|
||||
VFS.checkForEscape(fname);
|
||||
|
||||
char[] file = fname;
|
||||
|
||||
while(true)
|
||||
{
|
||||
// Find a path separator
|
||||
int ind = file.find('/');
|
||||
if(ind == -1)
|
||||
ind = file.find('\\');
|
||||
|
||||
if(ind == -1) break;
|
||||
|
||||
// The file is in a directory. Add it as a package.
|
||||
char[] packname = file[0..ind];
|
||||
file = file[ind+1..$];
|
||||
|
||||
// Empty directory name (eg. dir//file.mn or
|
||||
// dir/./file.mn) should not be added as packages.
|
||||
if(packname != "" && packname != ".")
|
||||
pack = pack.insertPackage(packname);
|
||||
|
||||
// Did we end with a path separator?
|
||||
if(file == "")
|
||||
fail("File name " ~ fname ~ " is a directory");
|
||||
}
|
||||
|
||||
// 'file' now contains the base filename, without the
|
||||
// directory
|
||||
assert(file.iEnds(".mn"));
|
||||
|
||||
// Pick away the extension
|
||||
file = file[0..$-3];
|
||||
|
||||
if(!cNameSet)
|
||||
// No class name given, set it to the derived name
|
||||
cname = file;
|
||||
else
|
||||
{
|
||||
// Both names were given, make sure they match
|
||||
if(cname.find('.') != -1)
|
||||
fail(format("Don't use a package specifier in the class name when the file name is also given (class %s, file %s)",
|
||||
cname, fname));
|
||||
|
||||
if(icmp(file,cname) != 0)
|
||||
fail(format("Class name %s does not match file name %s",
|
||||
cname, fname));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pick out the package part of the class name.
|
||||
char[] pname = cname;
|
||||
while(true)
|
||||
{
|
||||
int ind = find(pname, '.');
|
||||
if(ind != -1)
|
||||
{
|
||||
// Found a package name separator. Insert the package.
|
||||
pack = pack.insertPackage(pname[0..ind]);
|
||||
pname = pname[ind+1..$];
|
||||
|
||||
if(pname == "")
|
||||
fail("Class name cannot end with a period: " ~ cname);
|
||||
}
|
||||
else break;
|
||||
}
|
||||
|
||||
cname = pname;
|
||||
|
||||
// Derive the file name from the given class name.
|
||||
fname = pack.getPath(tolower(cname)) ~ ".mn";
|
||||
}
|
||||
|
||||
assert(cname != "" && !cname.iEnds(".mn"));
|
||||
assert(fname.iEnds(".mn"));
|
||||
|
||||
if(!isValidIdent(cname))
|
||||
fail(format("Invalid class name %s (file %s)", cname, fname));
|
||||
|
||||
// At this point, check if the class already exists.
|
||||
if(pack.ciInList(cname, mc))
|
||||
{
|
||||
// Match!
|
||||
assert(mc !is null);
|
||||
|
||||
// If the class name was given, we must have an exact match.
|
||||
if(cNameSet && (cname != mc.name.str))
|
||||
fail(format("Searched for %s but could only find case insensitive match %s",
|
||||
cname, mc.name.str));
|
||||
|
||||
// All is good, return the class.
|
||||
return mc;
|
||||
}
|
||||
|
||||
// No existing class. Search for the script file.
|
||||
if(!vfs.has(fname))
|
||||
{
|
||||
if(doThrow)
|
||||
fail("Cannot find script file " ~ fname);
|
||||
else return null;
|
||||
}
|
||||
|
||||
// Create a temporary file stream and load it
|
||||
auto bf = vfs.open(fname);
|
||||
auto ef = new EndianStream(bf);
|
||||
int bom = ef.readBOM();
|
||||
mc = new MonsterClass(pack);
|
||||
mc.parse(ef, fname, bom);
|
||||
delete bf;
|
||||
|
||||
// After the class is loaded, we can check its real name.
|
||||
|
||||
// If the name matches, we're done.
|
||||
if(cname == mc.name.str) return mc;
|
||||
|
||||
// Allow a case insensitive match if useCase is false or the name
|
||||
// was not given.
|
||||
if((!useCase || !cNameSet) && (icmp(cname, mc.name.str) == 0)) return mc;
|
||||
|
||||
// Oops, name mismatch
|
||||
fail(format("%s: Expected class name %s does not match loaded name %s",
|
||||
fname, cname, mc.name.str));
|
||||
assert(0);
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (config.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
singleton Config;
|
||||
|
||||
// Only some config options have been moved into Monster. Key bindings
|
||||
// and other low-level settings are still handled in D.
|
||||
|
||||
float musicVolume;
|
||||
float sfxVolume;
|
||||
float mainVolume;
|
||||
bool useMusic;
|
||||
|
||||
float mouseSensX;
|
||||
float mouseSensY;
|
||||
bool flipMouseY;
|
||||
|
||||
import sound;
|
||||
|
||||
// TODO: This could be replaced by some sort of hook placed on
|
||||
// mainVolume. Writing to the variable would automatically update
|
||||
// everything.
|
||||
setMainVolume(float f)
|
||||
{
|
||||
mainVolume = f;
|
||||
Music.updateVolume();
|
||||
}
|
||||
|
||||
setMusicVolume(float f)
|
||||
{
|
||||
musicVolume = f;
|
||||
Music.updateVolume();
|
||||
}
|
||||
|
||||
setSfxVolume(float f)
|
||||
{
|
||||
sfxVolume = f;
|
||||
// TODO: Update something here
|
||||
}
|
||||
|
||||
// Returns the "real" music volume
|
||||
float calcMusicVolume() { return mainVolume * musicVolume; }
|
@ -1,50 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (console.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class Console
|
||||
|
||||
|
||||
// This class contains all the functions available to the ingame
|
||||
// console.
|
||||
|
||||
import game
|
||||
|
||||
// Change player physics mode
|
||||
native walk();
|
||||
native fly();
|
||||
native ghost();
|
||||
|
||||
// Clear the console
|
||||
native clear();
|
||||
|
||||
// Set the console log font
|
||||
native setfont(char[] name);
|
||||
|
||||
// Exit the game
|
||||
native exit();
|
||||
quit() { exit(); }
|
||||
|
||||
// Toggle wireframe mode
|
||||
native wireframe();
|
||||
twf() { wireframe() }
|
||||
|
@ -1,35 +0,0 @@
|
||||
// Small script that prints the FPS to screen with regular intervals.
|
||||
import frames;
|
||||
|
||||
// Set up the text widget
|
||||
Widget txt = gui.text("StaticText",
|
||||
gui.getWidth()-90, 10, 120, 30,
|
||||
"Statistic");
|
||||
txt.setNeedMouseFocus(false);
|
||||
txt.setTextColor(1,1,1);
|
||||
txt.setCaption("hello!");
|
||||
|
||||
// Sleep until rendering begins. This just prevents the first printed
|
||||
// value from being 'nan'
|
||||
fsleep(0);
|
||||
|
||||
// counter and totalTime (in the 'frames' module) are updated
|
||||
// automatically by the system.
|
||||
ulong lastFrame = counter;
|
||||
float lastTime = totalTime;
|
||||
|
||||
float delay = 1.5;
|
||||
|
||||
while(true)
|
||||
{
|
||||
sleep(delay);
|
||||
|
||||
// Calculate differences since last frame
|
||||
ulong fdiff = counter-lastFrame;
|
||||
float tdiff = totalTime-lastTime;
|
||||
|
||||
txt.setCaption("fps: ", fdiff/tdiff);
|
||||
|
||||
lastFrame = counter;
|
||||
lastTime = totalTime;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (activator.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class Activator : GameObject;
|
||||
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (actor.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// Actors are a common base class for creatures, NPCs and the player.
|
||||
class Actor : GameObject;
|
||||
|
||||
// The actor's level
|
||||
int level;
|
||||
|
||||
// Stats. These are the base values, before any positive or negative
|
||||
// effects are applied.
|
||||
int
|
||||
baseStrength, baseIntelligence, baseWillpower, baseAgility,
|
||||
baseSpeed, baseEndurance, basePersonality, baseLuck;
|
||||
|
||||
// Maximum health values, before any effects are applied.
|
||||
int baseMaxHealth, baseMaxMana, baseMaxFatigue;
|
||||
|
||||
// Amount of gold this actor is carrying
|
||||
int gold;
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (apparatus.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// Covers all alchemy apparatus - mortars, retorts, etc
|
||||
class Apparatus : InventoryItem;
|
||||
|
||||
enum AppaType : char[] altName
|
||||
{
|
||||
MortarPestle = 0 : "Mortar and Pestle",
|
||||
Albemic = 1 : "Albemic"
|
||||
Calcinator = 2 : "Calcinator"
|
||||
}
|
||||
|
||||
float quality;
|
||||
int type;
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (armor.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// Covers all weapons and projectile weapons you can carry (like
|
||||
// arrows and throwable items.)
|
||||
class Armor : Repairable;
|
||||
|
||||
int type, armor;
|
@ -1,28 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (book.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class Book : EnchantItem;
|
||||
|
||||
bool isScroll;
|
||||
|
||||
int skillID; // Skill that is enhanced by reading this book, if any
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (clothing.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// All items that can be repaired (weapons and armor)
|
||||
class Clothing : EnchantItem;
|
||||
|
||||
int type;
|
||||
|
||||
enum Type
|
||||
{
|
||||
Pants = 0,
|
||||
Shoes = 1,
|
||||
Shirt = 2,
|
||||
Belt = 3,
|
||||
Robe = 4,
|
||||
RGlove = 5,
|
||||
LGlove = 6,
|
||||
Skirt = 7,
|
||||
Ring = 8,
|
||||
Amulet = 9
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (container.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class Container : LockedObject;
|
||||
|
||||
float weight; // Not sure, might be max total weight allowed?
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (creature.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class Creature : Actor;
|
||||
|
||||
// Soul gem value
|
||||
int soul;
|
||||
|
||||
// Not sure how to use these
|
||||
int combat, magic, stealth;
|
||||
|
||||
// Attack values for various types of attack
|
||||
int
|
||||
attackMin1, attackMax1,
|
||||
attackMin2, attackMax2,
|
||||
attackMin3, attackMax3;
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (door.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class Door : LockedObject;
|
||||
|
||||
// Does this door transport you to another cell?
|
||||
bool teleport;
|
||||
|
||||
// Door destination
|
||||
float destx, desty, destz;
|
||||
float destr1, destr2, destr3;
|
||||
char[] destCell;
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (enchantitem.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// Items that can be enchanted
|
||||
class EnchantItem : InventoryItem;
|
||||
|
||||
int enchant;
|
@ -1,79 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (gameobject.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// An object that exists inside a cell. All cell objects must have a
|
||||
// position in space.
|
||||
class GameObject;
|
||||
|
||||
// Is this object placed in a cell? isPlaced is true if the object is
|
||||
// displayed inside a cell with a mesh and given 3D coordinates, and
|
||||
// false otherwise (eg. if it is part of the player's inventory or in
|
||||
// a container.)
|
||||
|
||||
// TODO: This will change to an actual cell reference later on, and be
|
||||
// null if the object is not placed.
|
||||
bool isPlaced;
|
||||
|
||||
// Position and rotation in space (only valid if isPlaced is true.)
|
||||
float x, y, z;
|
||||
float r1, r2, r3;
|
||||
|
||||
float scale = 1.0;
|
||||
|
||||
char[] name, id;
|
||||
|
||||
// Various variables that are currently unused. Most of the strings
|
||||
// will be replaced by object references at some point.
|
||||
|
||||
// Owner of an object / activator
|
||||
char[] owner;
|
||||
|
||||
// A global variable? Don't know what it's used for.
|
||||
char[] glob;
|
||||
|
||||
// Reference to a soul trapped creature?
|
||||
char[] soulID;
|
||||
|
||||
// Faction owner? Rank?
|
||||
char[] cnam;
|
||||
int indx;
|
||||
|
||||
// Magic value / health / uses of an item?
|
||||
float xchg;
|
||||
|
||||
// These depend on the item in question
|
||||
int intv, nam9;
|
||||
int fltv;
|
||||
int unam;
|
||||
|
||||
// TODO: Scripts
|
||||
|
||||
// Cute hack for the console
|
||||
char[] pos()
|
||||
{
|
||||
// TODO: Make this simpler
|
||||
char[] xx = x;
|
||||
char[] yy = y;
|
||||
char[] zz = z;
|
||||
return "X:" ~ xx ~ " Y:" ~ yy ~ " Z:" ~ zz;
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (ingredient.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// Alchemy ingredients
|
||||
class Ingredient : InventoryItem;
|
||||
|
||||
// more to come here...
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (inventoryitem.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class InventoryItem : GameObject;
|
||||
|
||||
float weight;
|
||||
int value;
|
||||
|
||||
// Reference to current container. Player / NPC inventories are also
|
||||
// containers. Not used yet.
|
||||
Container holder;
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (light.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class Light : InventoryItem;
|
||||
|
||||
// Time left in seconds (for carried lights)
|
||||
float lifetime;
|
||||
|
||||
int radius;
|
||||
uint flags;
|
||||
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (lockedobject.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// Objects that can have a lock level and a trap
|
||||
class LockedObject : GameObject;
|
||||
|
||||
// ID of key and trap type.
|
||||
char[] key, trap;
|
||||
|
||||
int lockLevel;
|
@ -1,24 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (lockpick.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class Lockpick : Tool;
|
@ -1,30 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (misc.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
// Misc inventory items, like bottles, pots, pillows, keys etc. They
|
||||
// are mostly useless (except keys), but may be bought and sold.
|
||||
class Misc : InventoryItem;
|
||||
|
||||
// Not quite sure what the significance of this is. It is set to
|
||||
// non-zero for some keys, but not for all.
|
||||
int isKey;
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (npc.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class NPC : Person
|
||||
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (person.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class Person : Actor;
|
||||
|
||||
int disposition, reputation, rank;
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (player.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
singleton player : Person
|
||||
|
||||
name="Player Name"
|
||||
level = 5 // Just an example value
|
@ -1,26 +0,0 @@
|
||||
/*
|
||||
OpenMW - The completely unofficial reimplementation of Morrowind
|
||||
Copyright (C) 2008 Nicolay Korslund
|
||||
Email: < korslund@gmail.com >
|
||||
WWW: http://openmw.snaptoad.com/
|
||||
|
||||
This file (potion.mn) is part of the OpenMW package.
|
||||
|
||||
OpenMW is distributed as free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License
|
||||
version 3, as published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
version 3 along with this program. If not, see
|
||||
http://www.gnu.org/licenses/ .
|
||||
|
||||
*/
|
||||
|
||||
class Potion : InventoryItem;
|
||||
|
||||
int autoCalc;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue