Merge commit 'upstream/master'

pull/7/head
Marc Zinnschlag 15 years ago
commit d8c99c6ce3

23
.gitignore vendored

@ -1,23 +1,2 @@
screenshot*.png
*.o
*.a
build
*~
data
CMakeFiles
CMakeCache.txt
Makefile
cmake*.cmake
Ogre.log
ogre.cfg
build*
plugins.cfg
openmw.cfg
Doxygen
.thumbnails
resources
mwcompiler
mwinterpreter
clientconsole
MyGUI.log
mygui_test
mygui_test.png

@ -6,86 +6,6 @@ cmake_minimum_required(VERSION 2.6)
# Add path for CMake scripts
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/)
# source directory: apps
set(GAME
apps/openmw/main.cpp
apps/openmw/engine.cpp)
set(GAME_HEADER
apps/openmw/engine.hpp)
source_group(game FILES ${GAME} ${GAME_HEADER})
set(GAMEREND
apps/openmw/mwrender/mwscene.cpp
apps/openmw/mwrender/cellimp.cpp
apps/openmw/mwrender/interior.cpp
apps/openmw/mwrender/sky.cpp)
set(GAMEREND_HEADER
apps/openmw/mwrender/cell.hpp
apps/openmw/mwrender/cellimp.hpp
apps/openmw/mwrender/mwscene.hpp
apps/openmw/mwrender/interior.hpp
apps/openmw/mwrender/playerpos.hpp
apps/openmw/mwrender/sky.hpp)
source_group(apps\\openmw\\mwrender FILES ${GAMEREND} ${GAMEREND_HEADER})
# set(GAMEINPUT)
set(GAMEINPUT_HEADER
apps/openmw/mwinput/inputmanager.hpp)
source_group(apps\\openmw\\mwinput FILES ${GAMEINPUT} ${GAMEINPUT_HEADER})
set(GAMESCRIPT
apps/openmw/mwscript/scriptmanager.cpp
apps/openmw/mwscript/compilercontext.cpp
apps/openmw/mwscript/interpretercontext.cpp
apps/openmw/mwscript/cellextensions.cpp
apps/openmw/mwscript/miscextensions.cpp
apps/openmw/mwscript/guiextensions.cpp
apps/openmw/mwscript/extensions.cpp
apps/openmw/mwscript/globalscripts.cpp
)
set(GAMESCRIPT_HEADER
apps/openmw/mwscript/locals.hpp
apps/openmw/mwscript/scriptmanager.hpp
apps/openmw/mwscript/compilercontext.hpp
apps/openmw/mwscript/interpretercontext.hpp
apps/openmw/mwscript/cellextensions.hpp
apps/openmw/mwscript/miscextensions.hpp
apps/openmw/mwscript/guiextensions.hpp
apps/openmw/mwscript/extensions.hpp
apps/openmw/mwscript/globalscripts.hpp
)
source_group(apps\\openmw\\mwscript FILES ${GAMESCRIPT} ${GAMESCRIPT_HEADER})
set(GAMESOUND
apps/openmw/mwsound/soundmanager.cpp
apps/openmw/mwsound/extensions.cpp)
set(GAMESOUND_HEADER
apps/openmw/mwsound/soundmanager.hpp
apps/openmw/mwsound/extensions.hpp)
source_group(apps\\openmw\\mwsound FILES ${GAMESOUND} ${GAMESOUND_HEADER})
set(GAMEGUI
apps/openmw/mwgui/guimanager.cpp)
set(GAMEGUI_HEADER
apps/openmw/mwgui/guimanager.hpp)
source_group(apps\\openmw\\mwgui FILES ${GAMEGUI} ${GAMEGUI_HEADER})
set(GAMEWORLD
apps/openmw/mwworld/world.cpp)
set(GAMEWORLD_HEADER
apps/openmw/mwworld/refdata.hpp
apps/openmw/mwworld/world.hpp
apps/openmw/mwworld/ptr.hpp
apps/openmw/mwworld/environment.hpp
)
source_group(apps\\openmw\\mwworld FILES ${GAMEWORLD} ${GAMEWORLD_HEADER})
set(APPS ${GAME} ${GAMEREND} ${GAMEINPUT} ${GAMESCRIPT} ${GAMESOUND} ${GAMEGUI} ${GAMEWORLD})
set(APPS_HEADER ${GAME_HEADER} ${GAMEREND_HEADER} ${GAMEINPUT_HEADER} ${GAMESCRIPT_HEADER}
${GAMESOUND_HEADER} ${GAMEGUI_HEADER} ${GAMEWORLD_HEADER})
# source directory: components
set(COMP_DIR ${CMAKE_SOURCE_DIR}/components)
@ -136,6 +56,19 @@ set(OGRE_HEADER
${COMP_DIR}/engine/ogre/renderer.hpp)
source_group(components\\engine\\ogre FILES ${OGRE} ${OGRE_HEADER})
# components/mw_gui
set(MWGUI_HEADER
${COMP_DIR}/mw_gui/mw_layouts.hpp)
source_group(components\\mw_gui FILES ${MWGUI_HEADER})
# components/engine/gui
set(EGUI
${COMP_DIR}/engine/gui/manager.cpp)
set(EGUI_HEADER
${COMP_DIR}/engine/gui/manager.hpp
${COMP_DIR}/engine/gui/layout.hpp)
source_group(components\\engine\\gui FILES ${EGUI} ${EGUI_HEADER})
set(INPUT
${COMP_DIR}/engine/input/oismanager.cpp)
set(INPUT_HEADER
@ -171,17 +104,19 @@ file(GLOB INTERPRETER ${COMP_DIR}/interpreter/*.cpp)
file(GLOB INTERPRETER_HEADER ${COMP_DIR}/interpreter/*.hpp)
source_group(components\\interpreter FILES ${INTERPRETER} ${INTERPRETER_HEADER})
set(COMPONENTS ${BSA} ${NIF} ${NIFOGRE} ${ESM_STORE} ${OGRE} ${INPUT} ${MISC}
set(COMPONENTS ${BSA} ${NIF} ${NIFOGRE} ${ESM_STORE} ${OGRE} ${INPUT} ${MISC}
${EGUI}
${COMMANDSERVER}
${COMPILER}
${INTERPRETER})
set(COMPONENTS_HEADER ${BSA_HEADER} ${NIF_HEADER} ${NIFOGRE_HEADER} ${ESM_STORE_HEADER}
${ESM_HEADER} ${OGRE_HEADER} ${INPUT_HEADER} ${MISC_HEADER} ${COMPILER_HEADER}
${INTERPRETER_HEADER})
${INTERPRETER_HEADER} ${EGUI_HEADER} ${MWGUI_HEADER})
# source directory: libs
set(MANGLE_VFS libs/mangle/vfs/servers/ogre_vfs.cpp)
set(LIBDIR ${CMAKE_SOURCE_DIR}/libs)
set(MANGLE_VFS ${LIBDIR}/mangle/vfs/servers/ogre_vfs.cpp)
source_group(libs\\mangle_vfs FILES ${MANGLE_VFS})
set(OPENMW_LIBS ${MANGLE_VFS})
@ -244,34 +179,6 @@ if (CMAKE_COMPILER_IS_GNUCC)
add_definitions (-Wall)
endif (CMAKE_COMPILER_IS_GNUCC)
# Main executable
add_executable(openmw
# ???
# MACOSX_BUNDLE
${COMPONENTS} ${COMPONENTS_HEADER}
${OPENMW_LIBS} ${OPENMW_LIBS_HEADER}
${APPS} ${APPS_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)
# Other apps and tools
add_subdirectory( apps/clientconsole )
add_subdirectory( apps/mygui_dev )
# Apple bundling
if (APPLE)
set(MISC_FILES
@ -299,7 +206,19 @@ set(CMAKE_CXX_FLAGS "-arch i386")
endif (APPLE)
# Tools
# Apps and tools
add_subdirectory( apps/openmw )
option(BUILD_CLIENTCONSOLE "build external console for script interpreter" ON)
if (BUILD_CLIENTCONSOLE)
add_subdirectory( apps/clientconsole )
endif()
option(BUILD_MYGUI_TEST "build test program for MyGUI" ON)
if (BUILD_MYGUI_TEST)
add_subdirectory( apps/mygui_dev )
endif()
option(BUILD_MWCOMPILER "build standalone Morrowind script compiler" ON)
if (BUILD_MWCOMPILER)
add_subdirectory( apps/mwcompiler )

@ -0,0 +1,2 @@
old
run.sh

@ -2,6 +2,7 @@ add_executable(mygui_test
main.cpp
${BSA} ${BSA_HEADER}
${OGRE} ${OGRE_HEADER}
${EGUI} ${EGUI_HEADER}
)
target_link_libraries(mygui_test
${OGRE_LIBRARIES}

@ -1,14 +1,12 @@
#include <iostream>
#include <string>
#include <vector>
using namespace std;
#include "manager.hpp"
#include "layout.hpp"
#include "mw_layouts.hpp"
#include <components/engine/gui/manager.hpp>
#include <components/mw_gui/mw_layouts.hpp>
#include <components/engine/ogre/renderer.hpp>
#include <OgreResourceGroupManager.h>
#include <OgreRenderWindow.h>
#include <components/bsa/bsa_archive.hpp>
@ -26,7 +24,7 @@ struct Listener : public Ogre::FrameListener
total += evt.timeSinceLastFrame;
// Countdown to exit
const int MAX = 5;
const int MAX = 4;
if(total >= step)
{
step++;

@ -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)

@ -70,11 +70,6 @@ namespace GUI
void shutdown()
{
for (VectorBasePtr::iterator iter=mListBase.begin(); iter!=mListBase.end(); ++iter) {
delete (*iter);
}
mListBase.clear();
MyGUI::LayoutManager::getInstance().unloadLayout(mListWindowRoot);
mListWindowRoot.clear();
}
@ -118,8 +113,6 @@ namespace GUI
std::string mPrefix;
std::string mLayoutName;
MyGUI::VectorWidgetPtr mListWindowRoot;
typedef std::vector<Layout*> VectorBasePtr;
VectorBasePtr mListBase;
};
}
#endif

@ -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(&section) + 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…
Cancel
Save