You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1423 lines
37 KiB
D
1423 lines
37 KiB
D
/*
|
|
Monster - an advanced game scripting language
|
|
Copyright (C) 2007-2009 Nicolay Korslund
|
|
Email: <korslund@gmail.com>
|
|
WWW: http://monster.snaptoad.com/
|
|
|
|
This file (scopes.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.scopes;
|
|
|
|
import std.stdio;
|
|
import std.string;
|
|
|
|
import monster.util.aa;
|
|
import monster.options;
|
|
|
|
import monster.compiler.statement;
|
|
import monster.compiler.expression;
|
|
import monster.compiler.tokenizer;
|
|
import monster.compiler.types;
|
|
import monster.compiler.assembler;
|
|
import monster.compiler.properties;
|
|
import monster.compiler.functions;
|
|
import monster.compiler.states;
|
|
import monster.compiler.structs;
|
|
import monster.compiler.enums;
|
|
import monster.compiler.operators;
|
|
import monster.compiler.variables;
|
|
|
|
import monster.vm.mclass;
|
|
import monster.vm.error;
|
|
import monster.vm.vm;
|
|
|
|
// The global scope
|
|
RootScope global;
|
|
|
|
void initScope()
|
|
{
|
|
global = new RootScope;
|
|
}
|
|
|
|
// List all identifier types
|
|
enum LType
|
|
{
|
|
None,
|
|
Class,
|
|
Type,
|
|
Variable,
|
|
Function,
|
|
State,
|
|
StateLabel,
|
|
LoopLabel,
|
|
Property,
|
|
Import,
|
|
Package,
|
|
}
|
|
|
|
const char[][] LTypeName =
|
|
[
|
|
LType.None: "",
|
|
LType.Class: "class",
|
|
LType.Type: "type",
|
|
LType.Variable: "variable",
|
|
LType.Function: "function",
|
|
LType.State: "state",
|
|
LType.StateLabel: "state label",
|
|
LType.LoopLabel: "loop label",
|
|
LType.Property: "property",
|
|
LType.Package: "package",
|
|
];
|
|
|
|
struct ScopeLookup
|
|
{
|
|
Token name;
|
|
LType ltype;
|
|
|
|
Scope sc;
|
|
Type type;
|
|
union
|
|
{
|
|
MonsterClass mc;
|
|
Variable* var;
|
|
Function* func;
|
|
State* state;
|
|
StateLabel *slabel;
|
|
ImportHolder imphold;
|
|
|
|
void *ptr;
|
|
Object ob;
|
|
}
|
|
|
|
bool isClass() { return ltype == LType.Class; }
|
|
bool isType() { return ltype == LType.Type; }
|
|
bool isVar() { return ltype == LType.Variable; }
|
|
bool isFunc() { return ltype == LType.Function; }
|
|
bool isState() { return ltype == LType.State; }
|
|
bool isNone() { return ltype == LType.None; }
|
|
bool isImport() { return ltype == LType.Import; }
|
|
bool isPackage() { return ltype == LType.Package; }
|
|
bool isProperty()
|
|
{
|
|
bool ret = (ltype == LType.Property);
|
|
assert( ret == (cast(PropertyScope)sc !is null) );
|
|
return ret;
|
|
}
|
|
|
|
// For properties only
|
|
Type getPropType(Type owner)
|
|
{
|
|
assert(type is null);
|
|
assert(owner !is null);
|
|
assert(name.str != "");
|
|
|
|
type = owner;
|
|
return ps().getType(name.str, owner);
|
|
}
|
|
private PropertyScope ps()
|
|
{
|
|
assert(isProperty);
|
|
assert(type !is null);
|
|
return cast(PropertyScope)sc;
|
|
}
|
|
bool isPropLValue() { return ps().isLValue(name.str, type); }
|
|
bool isPropStatic() { return ps().isStatic(name.str, type); }
|
|
void getPropValue() { ps().getValue(name.str, type); }
|
|
void setPropValue() { ps().setValue(name.str, type); }
|
|
|
|
static ScopeLookup opCall(Token nm, LType lt, Type tp, Scope sc, Object ob)
|
|
{
|
|
auto sl = ScopeLookup(nm, lt, tp, sc);
|
|
sl.ob = ob;
|
|
return sl;
|
|
}
|
|
|
|
static ScopeLookup opCall(Token nm, LType lt, Type tp, Scope sc, void*p = null)
|
|
{
|
|
assert(nm.str != "");
|
|
|
|
ScopeLookup sl;
|
|
sl.name = nm;
|
|
sl.ltype = lt;
|
|
sl.type = tp;
|
|
sl.sc = sc;
|
|
sl.ptr = p;
|
|
return sl;
|
|
}
|
|
}
|
|
|
|
|
|
// TODO: Write here which of these should be kept around at runtime,
|
|
// and which of them can be discarded after compilation.
|
|
|
|
abstract class Scope
|
|
{
|
|
protected:
|
|
// The parent scope. For function scopes, this is the scope of the
|
|
// class it belongs to. For code blocks, loops etc, it is the scope
|
|
// of the code block outside this one. For classes, this points to
|
|
// the scope of the parent class, if any, or to the package or
|
|
// global scope.
|
|
Scope parent;
|
|
|
|
// Made protected since it is so easy to confuse with isStateCode(),
|
|
// and we never actually need it anywhere outside this file.
|
|
bool isState() { return false; }
|
|
|
|
private:
|
|
// Name of this scope. All scopes must have a name, but for some
|
|
// types it is set automatically (like a code block scope.) It is
|
|
// mostly used for debugging.
|
|
char[] scopeName;
|
|
|
|
ImportHolder importList[];
|
|
|
|
public:
|
|
|
|
this(Scope last, char[] name)
|
|
{
|
|
scopeName = name;
|
|
parent = last;
|
|
|
|
assert(last !is this, "scope cannot be it's own parent");
|
|
assert(name != "");
|
|
|
|
// Copy the import list from our parent
|
|
if(!isRoot)
|
|
importList = parent.importList.dup;
|
|
}
|
|
|
|
// Verify that an identifier is not declared in this scope. If the
|
|
// identifier is found, give a duplicate identifier compiler
|
|
// error. Recurses through parent scopes.
|
|
final void clearId(Token name)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("ClearId %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
// Lookup checks all parent scopes so we only have to call it
|
|
// once.
|
|
auto sl = lookup(name, false);
|
|
|
|
if(!sl.isNone)
|
|
{
|
|
if(sl.isProperty)
|
|
fail(name.str ~ " is a property and cannot be redeclared",
|
|
name.loc);
|
|
|
|
char[] oldLoc = name.loc.toString;
|
|
|
|
// There might be a case insensitive match. In that case we
|
|
// should print the original name as well.
|
|
if(sl.name.str != name.str)
|
|
oldLoc ~= " as " ~ sl.name.str;
|
|
|
|
fail(format("%s is already declared as a %s (at %s)",
|
|
name.str, LTypeName[sl.ltype], oldLoc),
|
|
name.loc);
|
|
}
|
|
assert(sl.name.str == name.str);
|
|
}
|
|
|
|
// Is this the root scope?
|
|
final bool isRoot()
|
|
{
|
|
if(parent !is null) return false;
|
|
assert(allowRoot(), toString() ~ " cannot be a root scope");
|
|
return true;
|
|
}
|
|
|
|
// Is THIS scope of this particular kind?
|
|
bool isFunc() { return false; }
|
|
bool isCode() { return false; }
|
|
bool isLoop() { return false; }
|
|
bool isClass() { return false; }
|
|
bool isArray() { return false; }
|
|
bool isStruct() { return false; }
|
|
bool isPackage() { return false; }
|
|
bool isProperty() { return false; }
|
|
|
|
// Is this scope allowed to be a root scope (without parent?)
|
|
bool allowRoot() { return false; }
|
|
|
|
// Get the function definition belonging to this scope.
|
|
Function *getFunction()
|
|
{
|
|
assert(!isRoot(), "getFunction called on a root scope");
|
|
return parent.getFunction();
|
|
}
|
|
|
|
// Get the class
|
|
MonsterClass getClass()
|
|
{
|
|
assert(!isRoot(), "getClass called on a root scope");
|
|
return parent.getClass();
|
|
}
|
|
|
|
State* getState()
|
|
{
|
|
assert(!isRoot(), "getState called on a root scope");
|
|
return parent.getState();
|
|
}
|
|
|
|
ArrayOperator getArray()
|
|
{
|
|
assert(!isRoot(), "getArray called on wrong scope type");
|
|
return parent.getArray();
|
|
}
|
|
|
|
int getLoopStack()
|
|
{
|
|
assert(!isRoot(), "getLoopStack called on wrong scope type");
|
|
return parent.getLoopStack();
|
|
}
|
|
|
|
// Get the break or continue label for the given named loop, or the
|
|
// innermost loop if name is empty or omitted. Returns null if the
|
|
// label was not found. Can only be called within loops.
|
|
LabelStatement getBreak(char[] name = "") { return null; }
|
|
LabelStatement getContinue(char[] name = "") { return null; }
|
|
|
|
// Lookup a string
|
|
final ScopeLookup lookupName(char[] name)
|
|
{ return lookup(Token(name, Floc.init)); }
|
|
|
|
// Look up a token in this scope. If autoLoad is true, load classes
|
|
// automatically if a file exists.
|
|
ScopeLookup lookup(Token name, bool autoLoad=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
if(isRoot()) return ScopeLookup(name, LType.None, null, null);
|
|
else return parent.lookup(name, autoLoad);
|
|
}
|
|
|
|
// Look up a token in this scope. If the token is not found, try the
|
|
// lookup again and check the file system for classes.
|
|
ScopeLookup lookupClass(Token name)
|
|
{
|
|
auto sl = lookup(name);
|
|
if(sl.isNone)
|
|
sl = lookup(name, true);
|
|
return sl;
|
|
}
|
|
|
|
// Look up an identifier, and check imported scopes as well.
|
|
ScopeLookup lookupImport(Token name, bool second=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("LookupImport %s in %s (line %s)", name, this, __LINE__);
|
|
auto l = lookup(name, second);
|
|
if(!l.isNone) return l;
|
|
|
|
// Nuttin' was found, try the imports
|
|
bool found = false;
|
|
auto old = l;
|
|
foreach(ind, imp; importList)
|
|
{
|
|
static if(traceLookups)
|
|
{
|
|
writefln(" LI: Import is: %s, index %s of %s",
|
|
imp, ind, importList.length-1);
|
|
}
|
|
l = imp.lookup(name, second);
|
|
|
|
// Only accept types, classes, variables and functions
|
|
if(l.isType || l.isClass || l.isVar || l.isFunc)
|
|
{
|
|
// Duplicate matches aren't allowed
|
|
if(found && l.sc !is old.sc)
|
|
fail(format(
|
|
"%s matches both %s.%s (at %s) and %s.%s (at %s)", name.str,
|
|
old.imphold.getName(), old.name.str, old.name.loc,
|
|
imp.getName(), l.name.str, l.name.loc),
|
|
name.loc);
|
|
|
|
// First match
|
|
found = true;
|
|
old = l;
|
|
old.imphold = imp;
|
|
}
|
|
}
|
|
|
|
if(!found)
|
|
{
|
|
// If we haven't tried searching the file system for
|
|
// classes, try that.
|
|
if(!second)
|
|
return lookupImport(name, true);
|
|
|
|
return ScopeLookup(name, LType.None, null, null);
|
|
}
|
|
|
|
// Tell the caller that this is an import. We override the
|
|
// lookup struct, but it doesn't matter since the lookup will
|
|
// have to be performed again anyway.
|
|
old.ltype = LType.Import;
|
|
assert(old.imphold !is null);
|
|
return old;
|
|
}
|
|
|
|
// More user-friendly version for API-defined import of classes.
|
|
// Eg. global.registerImport(myclass) -> makes myclass available in
|
|
// ALL classes.
|
|
void registerImport(MonsterClass mc)
|
|
{
|
|
// Check if this class is already implemented
|
|
foreach(imp; importList)
|
|
if(imp.isClass(mc)) return;
|
|
|
|
static if(traceLookups)
|
|
writefln("Importing class %s into %s", mc, this);
|
|
importList ~= new ClassImpHolder(mc);
|
|
}
|
|
|
|
// Ditto for packages
|
|
void registerImport(PackageScope psc)
|
|
{
|
|
// Never import the global scope - it's already implied as a
|
|
// parent scope of most other scopes
|
|
//if(psc is global) return;
|
|
|
|
foreach(imp; importList)
|
|
if(imp.isPack(psc)) return;
|
|
|
|
static if(traceLookups)
|
|
writefln("Importing package %s into %s", psc, this);
|
|
|
|
importList ~= new PackageImpHolder(psc);
|
|
}
|
|
|
|
// Even more user-friendly version for classes. Takes a list of
|
|
// class names.
|
|
void registerImport(char[][] cls ...)
|
|
{
|
|
foreach(c; cls)
|
|
registerImport(vm.load(c));
|
|
}
|
|
|
|
// Used for summing up stack level. Redeclared in StackScope.
|
|
int getTotLocals() { return 0; }
|
|
int getLocals() { assert(0); }
|
|
|
|
// This must be overridden in all scopes that allow variable
|
|
// declarations.
|
|
int addNewVar(int varSize) { assert(0); }
|
|
|
|
final:
|
|
|
|
bool isStateCode()
|
|
{ return isInState() && !isInFunc(); }
|
|
|
|
// Is this scope OR one of the parent scopes of the given kind?
|
|
bool isInFunc()
|
|
{
|
|
if(isFunc()) return true;
|
|
if(isRoot()) return false;
|
|
return parent.isInFunc();
|
|
}
|
|
bool isInState()
|
|
{
|
|
if(isState()) return true;
|
|
if(isRoot()) return false;
|
|
return parent.isInState();
|
|
}
|
|
bool isInLoop()
|
|
{
|
|
if(isLoop()) return true;
|
|
if(isRoot()) return false;
|
|
return parent.isInLoop();
|
|
}
|
|
bool isInClass()
|
|
{
|
|
if(isClass()) return true;
|
|
if(isRoot()) return false;
|
|
return parent.isInClass();
|
|
}
|
|
bool isInPackage()
|
|
{
|
|
if(isPackage()) return true;
|
|
if(isRoot()) return false;
|
|
return parent.isInPackage();
|
|
}
|
|
|
|
char[] toString()
|
|
{
|
|
if(parent is null) return scopeName;
|
|
else return parent.toString() ~ "." ~ scopeName;
|
|
}
|
|
}
|
|
|
|
final class StateScope : Scope
|
|
{
|
|
private:
|
|
State* st;
|
|
|
|
public:
|
|
|
|
this(Scope last, State* s)
|
|
{
|
|
st = s;
|
|
super(last, st.name.str);
|
|
}
|
|
|
|
// Insert a label, check for name collisions.
|
|
void insertLabel(StateLabel *lb)
|
|
{
|
|
// Check for name collisions
|
|
clearId(lb.name);
|
|
|
|
st.labels[lb.name.str] = lb;
|
|
}
|
|
|
|
override:
|
|
ScopeLookup lookup(Token name, bool autoLoad=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
assert(name.str != "");
|
|
|
|
// Check against state labels
|
|
StateLabel *lb;
|
|
if(st.labels.inList(name.str, lb))
|
|
return ScopeLookup(lb.name, LType.StateLabel, null, this, lb);
|
|
|
|
return super.lookup(name, autoLoad);
|
|
}
|
|
|
|
State* getState() { return st; }
|
|
|
|
bool isState() { return true; }
|
|
}
|
|
|
|
final class RootScope : PackageScope
|
|
{
|
|
private:
|
|
// Lookup by integer index. The indices are global, that is why they
|
|
// are stored in the root scope rather than in the individual
|
|
// packages.
|
|
HashTable!(CIndex, MonsterClass) indexList;
|
|
|
|
// Forward references. Refers to unloaded and non-existing
|
|
// classes. TODO: This mechanism probably won't work very well with
|
|
// multiple packages. It should be possible to have forward
|
|
// references to multiple classes with the same name, as long as
|
|
// they are in the correct packages. Think it over some more.
|
|
HashTable!(char[], CIndex) forwards;
|
|
|
|
// Unique global index to give the next class.
|
|
CIndex next = 1;
|
|
|
|
public:
|
|
this() { super(null, "global", ""); }
|
|
bool allowRoot() { return true; }
|
|
|
|
// Get a class by index
|
|
MonsterClass getClass(CIndex ind)
|
|
{
|
|
MonsterClass mc;
|
|
if(!indexList.inList(ind, mc))
|
|
fail("Invalid class index encountered");
|
|
mc.requireScope();
|
|
return mc;
|
|
}
|
|
|
|
// Gets the index of a class, or inserts a forward reference to it
|
|
// if cannot be found. Subsequent calls for the same name will
|
|
// insert the same index, and when/if the class is actually loaded
|
|
// it will get the same index.
|
|
CIndex getForwardIndex(char[] name)
|
|
{
|
|
// Called when an existing forward does not exist
|
|
void inserter(ref CIndex v)
|
|
{ v = next++; }
|
|
|
|
// Return the index in forwards, or create a new one if none
|
|
// exists.
|
|
return forwards.get(name, &inserter);
|
|
}
|
|
|
|
// Returns true if a given class has been inserted into the scope.
|
|
bool isLoaded(CIndex ci)
|
|
{
|
|
return indexList.inList(ci);
|
|
}
|
|
|
|
override ScopeLookup lookup(Token name, bool autoLoad=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
// Basic types are looked up here
|
|
if(BasicType.isBasic(name.str))
|
|
return ScopeLookup(name, LType.Type, BasicType.get(name.str), this);
|
|
|
|
// Return ourselves as the package named 'global'
|
|
if(name.str == "global")
|
|
return ScopeLookup(name, LType.Package, type, this);
|
|
|
|
// No parents to check
|
|
assert(isRoot());
|
|
return super.lookup(name, autoLoad);
|
|
}
|
|
}
|
|
|
|
// A package scope is a scope that can contain classes.
|
|
class PackageScope : Scope
|
|
{
|
|
private:
|
|
// List of classes in this package. This is case insensitive, so we
|
|
// can look up file names too.
|
|
HashTable!(char[], MonsterClass, GCAlloc, CITextHash) classes;
|
|
|
|
// List of sub-packages
|
|
HashTable!(char[], PackageScope, GCAlloc, CITextHash) children;
|
|
|
|
char[] path;
|
|
|
|
public:
|
|
// The type is used for expression lookups, eg. packname.Class(obj);
|
|
PackageType type;
|
|
|
|
this(Scope last, char[] name, char[] dir)
|
|
{
|
|
super(last, name);
|
|
|
|
auto ps = cast(PackageScope) last;
|
|
assert(last is null || ps !is null,
|
|
"Packages can only have other packages as parent scopes");
|
|
|
|
path = dir;
|
|
assert(!path.begins("/"));
|
|
|
|
assert(vm.vfs !is null);
|
|
if(dir != "" && !vm.vfs.hasDir(dir))
|
|
fail("Cannot create package "~toString~": directory "
|
|
~dir~" not found");
|
|
|
|
type = new PackageType(this);
|
|
}
|
|
|
|
bool isPackage() { return true; }
|
|
|
|
char[] getPath(char[] file)
|
|
{
|
|
if(path == "") return file;
|
|
|
|
file = path ~ "/" ~ file;
|
|
assert(file[0] != '/');
|
|
return file;
|
|
}
|
|
|
|
// Insert a new sub-package, or find an existing one. All package
|
|
// names are case insensitive. Returns the package scope. Fails if
|
|
// there are naming conflicts with other identifiers.
|
|
PackageScope insertPackage(char[] name)
|
|
{
|
|
auto sl = lookupName(name);
|
|
|
|
if(!sl.isPackage)
|
|
fail(format("Package name %s is already declared (at %s) as a %s",
|
|
sl.name.str, sl.name.loc, LTypeName[sl.ltype]));
|
|
if(sl.isNone)
|
|
fail("Cannot find package " ~ name);
|
|
|
|
auto ps = cast(PackageScope)sl.sc;
|
|
assert(ps !is null);
|
|
return ps;
|
|
}
|
|
|
|
// Used internally from lookup()
|
|
private PackageScope makeSubPackage(char[] name)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
if(!isValidIdent(name))
|
|
fail("Cannot create package " ~ name ~ ": not a valid identifier");
|
|
|
|
assert(!children.inList(name));
|
|
|
|
PackageScope ps;
|
|
ps = new PackageScope(this, name, getPath(name));
|
|
children[name] = ps;
|
|
|
|
return ps;
|
|
}
|
|
|
|
// Insert a new class into the scope. The class is given a unique
|
|
// global index. If the class was previously forward referenced, the
|
|
// forward is replaced and the previously assigned forward index is
|
|
// used. A class can only be inserted once.
|
|
void insertClass(MonsterClass cls)
|
|
{
|
|
assert(cls !is null);
|
|
assert(cls.name.str != "");
|
|
|
|
// Are we already in the list?
|
|
MonsterClass c2;
|
|
if(ciInList(cls.name.str, c2))
|
|
{
|
|
// That's not allowed. Determine what error message to give.
|
|
|
|
if(c2.name.str == cls.name.str)
|
|
// Exact name collision
|
|
fail("Cannot load class " ~ cls.name.str ~
|
|
" because it is already loaded.");
|
|
|
|
// Case insensitive match
|
|
fail("Cannot load class " ~ cls.name.str ~ " because "
|
|
~ c2.name.str ~
|
|
" already exists (class names cannot differ only by case.)");
|
|
}
|
|
|
|
// Check that no other identifier with this name exists
|
|
clearId(cls.name);
|
|
|
|
// We're clear. Find an index to use. If the class was forward
|
|
// referenced, then an index has already been assigned.
|
|
CIndex ci;
|
|
|
|
if(global.forwards.inList(cls.name.str, ci))
|
|
{
|
|
if(this !is global)
|
|
fail("Class " ~ cls.name.str ~ " was forward referenced and can only be added to the 'global' package, not to " ~ toString);
|
|
|
|
// ci is set, remove the entry from the forwards hashmap
|
|
assert(ci != 0);
|
|
global.forwards.remove(cls.name.str);
|
|
}
|
|
else
|
|
// Get a new index
|
|
ci = global.next++;
|
|
|
|
assert(!global.indexList.inList(ci));
|
|
|
|
// Assign the index and insert class into both lists
|
|
cls.gIndex = ci;
|
|
classes[cls.name.str] = cls;
|
|
global.indexList[ci] = cls;
|
|
|
|
assert(global.indexList.inList(ci));
|
|
}
|
|
|
|
// Case insensitive lookups. Used for comparing with file names,
|
|
// before the actual class is loaded.
|
|
bool ciInList(char[] name)
|
|
{ return classes.inList(name); }
|
|
bool ciInList(char[] name, out MonsterClass cb)
|
|
{ return classes.inList(name, cb); }
|
|
|
|
// Case sensitive versions. If a class is found that does not match
|
|
// in case, it is an error.
|
|
bool csInList(char[] name, out MonsterClass cb)
|
|
{
|
|
bool result = ciInList(name, cb) && cb.name.str == name;
|
|
|
|
// The caller depends on the output to be null if the search
|
|
// fails.
|
|
if(!result) cb = null;
|
|
|
|
return result;
|
|
}
|
|
|
|
override ScopeLookup lookup(Token name, bool autoLoad=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
// Look up packages
|
|
PackageScope psc;
|
|
if(!children.inList(name.str, psc))
|
|
// Check the file system if there is a matching directory
|
|
if(vm.vfs.hasDir(getPath(name.str)))
|
|
psc = makeSubPackage(name.str);
|
|
|
|
if(psc !is null)
|
|
return ScopeLookup(name, LType.Package, psc.type, psc);
|
|
|
|
// Look up classes
|
|
MonsterClass mc;
|
|
if(!csInList(name.str, mc) && autoLoad)
|
|
{
|
|
// Load the class from file, if it exists
|
|
mc = vm.loadNoFail(name.str,
|
|
tolower(getPath(name.str))~".mn");
|
|
}
|
|
|
|
if(mc !is null)
|
|
return ScopeLookup(mc.name, LType.Class, null, this, mc);
|
|
|
|
// Unlike other scopes, a package scope doesn't check through
|
|
// all its parent packages. That would mean that a name search
|
|
// for a package or a class could 'leak' out of a directory and
|
|
// find the wrong match. Instead we jump directly to the root
|
|
// scope.
|
|
|
|
// If we're the root scope, super.lookup will return an empty
|
|
// result.
|
|
if(isRoot()) return super.lookup(name, autoLoad);
|
|
|
|
// Otherwise, jump directly to root, do not collect 200 dollars.
|
|
assert(global !is this);
|
|
return global.lookup(name, autoLoad);
|
|
}
|
|
}
|
|
|
|
// A scope that can contain variables.
|
|
abstract class VarScope : Scope
|
|
{
|
|
private:
|
|
HashTable!(char[], Variable*) variables;
|
|
|
|
public:
|
|
|
|
this(Scope last, char[] name)
|
|
{ super(last, name); }
|
|
|
|
// Insert a variable, checks if it already exists.
|
|
void insertVar(Variable* dec)
|
|
{
|
|
assert(!isStateCode, "called insertVar in state code");
|
|
|
|
// Check for name collisions.
|
|
clearId(dec.name);
|
|
|
|
variables[dec.name.str] = dec;
|
|
}
|
|
|
|
override:
|
|
|
|
ScopeLookup lookup(Token name, bool autoLoad=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
assert(name.str != "");
|
|
|
|
Variable *vd;
|
|
if(variables.inList(name.str, vd))
|
|
return ScopeLookup(vd.name, LType.Variable, vd.type, this, vd);
|
|
|
|
return super.lookup(name, autoLoad);
|
|
}
|
|
}
|
|
|
|
// A scope that can contain functions and variables
|
|
class FVScope : VarScope
|
|
{
|
|
protected:
|
|
HashTable!(char[], Function*) functions;
|
|
|
|
public:
|
|
this(Scope last, char[] name)
|
|
{ super(last, name); }
|
|
|
|
// Insert a function.
|
|
void insertFunc(Function* fd)
|
|
{
|
|
if(isClass)
|
|
{
|
|
// Are we overriding a function?
|
|
auto look = lookup(fd.name);
|
|
if(look.isFunc)
|
|
// We're overriding. Let fd know, and let it handle the
|
|
// details when it resolves.
|
|
fd.overrides = look.func;
|
|
else
|
|
// No matching function. Check that the name is available.
|
|
clearId(fd.name);
|
|
}
|
|
else
|
|
// Non-class functions can never override anything
|
|
clearId(fd.name);
|
|
|
|
fd.index = functions.length;
|
|
|
|
// Store the function definition
|
|
functions[fd.name.str] = fd;
|
|
}
|
|
|
|
override:
|
|
ScopeLookup lookup(Token name, bool autoLoad=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
assert(name.str != "");
|
|
|
|
Function* fd;
|
|
|
|
if(functions.inList(name.str, fd))
|
|
return ScopeLookup(fd.name, LType.Function, fd.type, this, fd);
|
|
|
|
// Let VarScope handle variables
|
|
return super.lookup(name, autoLoad);
|
|
}
|
|
}
|
|
|
|
// Can contain types, functions and variables. 'Types' means structs,
|
|
// enums and other user-generated sub-types (may also include classes
|
|
// later.)
|
|
|
|
// The TFV, FV and Var scopes might (probably will) be merged at some
|
|
// point. I'm just keeping them separate for clearity while the scope
|
|
// structure is being developed.
|
|
class TFVScope : FVScope
|
|
{
|
|
private:
|
|
HashTable!(char[], StructType) structs;
|
|
HashTable!(char[], EnumType) enums;
|
|
|
|
public:
|
|
this(Scope last, char[] name)
|
|
{ super(last, name); }
|
|
|
|
void insertStruct(StructDeclaration sd)
|
|
{
|
|
clearId(sd.name);
|
|
structs[sd.name.str] = sd.type;
|
|
}
|
|
|
|
void insertEnum(EnumType sd)
|
|
{
|
|
clearId(sd.nameTok);
|
|
enums[sd.name] = sd;
|
|
}
|
|
|
|
override:
|
|
ScopeLookup lookup(Token name, bool autoLoad=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
assert(name.str != "");
|
|
|
|
StructType sd;
|
|
EnumType ed;
|
|
Type tp;
|
|
|
|
if(structs.inList(name.str, sd)) tp = sd;
|
|
else if(enums.inList(name.str, ed)) tp = ed;
|
|
|
|
if(tp !is null)
|
|
return ScopeLookup(Token(tp.name, tp.loc), LType.Type, tp, this);
|
|
|
|
// Pass it on to the parent
|
|
return super.lookup(name, autoLoad);
|
|
}
|
|
}
|
|
|
|
// Lookup scope for enums. For simplicity we use the property system
|
|
// to handle enum members.
|
|
final class EnumScope : SimplePropertyScope
|
|
{
|
|
this(EnumType _et)
|
|
{
|
|
super("EnumScope", GenericProperties.singleton);
|
|
et = _et;
|
|
|
|
insert("value", "long", &enumVal);
|
|
inserts("length", "int", &enumLen);
|
|
inserts("first", et, &enumFirst);
|
|
inserts("last", et, &enumLast);
|
|
inserts("min", "long", &enumMin);
|
|
inserts("max", "long", &enumMax);
|
|
}
|
|
|
|
void enumMin()
|
|
{ tasm.push8(et.minVal); }
|
|
|
|
void enumMax()
|
|
{ tasm.push8(et.maxVal); }
|
|
|
|
void enumFirst()
|
|
{ tasm.push(et.entries[0].index); }
|
|
|
|
void enumLast()
|
|
{ tasm.push(et.entries[$-1].index); }
|
|
|
|
void enumLen()
|
|
{ tasm.push(cast(uint)et.entries.length); }
|
|
|
|
void enumVal()
|
|
{ tasm.getEnumValue(et); }
|
|
|
|
EnumType et;
|
|
|
|
override:
|
|
|
|
// Intercept all the enum entry names when used as properties
|
|
Type getType(char[] name, Type oType)
|
|
{
|
|
// Is it one of the enum values?
|
|
auto ee = et.lookup(name);
|
|
if(ee !is null)
|
|
{
|
|
assert(et is oType ||
|
|
et.getMeta() is oType);
|
|
return et;
|
|
}
|
|
|
|
// Maybe one of the fields?
|
|
int fe = et.findField(name);
|
|
if(fe != -1)
|
|
return et.fields[fe].type;
|
|
|
|
return super.getType(name, oType);
|
|
}
|
|
|
|
void getValue(char[] name, Type oType)
|
|
{
|
|
auto ee = et.lookup(name);
|
|
if(ee !is null)
|
|
{
|
|
assert(et is oType ||
|
|
et.getMeta() is oType);
|
|
tasm.push(ee.index);
|
|
return;
|
|
}
|
|
|
|
int fe = et.findField(name);
|
|
if(fe != -1)
|
|
{
|
|
// We're getting a field from a variable. Eg.
|
|
// en.errorString. The enum index is on the stack, and
|
|
// getEnumValue supplies the enum type and the field
|
|
// index. The VM can find the correct field value from that.
|
|
tasm.getEnumValue(et, fe);
|
|
return;
|
|
}
|
|
|
|
super.getValue(name, oType);
|
|
}
|
|
|
|
bool hasProperty(char[] name)
|
|
{
|
|
auto ee = et.lookup(name);
|
|
if(ee !is null) return true;
|
|
if(et.findField(name) != -1)
|
|
return true;
|
|
return super.hasProperty(name);
|
|
}
|
|
|
|
bool isStatic(char[] name, Type oType)
|
|
{
|
|
auto ee = et.lookup(name);
|
|
if(ee !is null)
|
|
{
|
|
assert(et is oType ||
|
|
et.getMeta() is oType);
|
|
return true;
|
|
}
|
|
|
|
// The fields are always non-static, they depend on the actual
|
|
// enum value supplied.
|
|
if(et.findField(name) != -1)
|
|
return false;
|
|
|
|
return super.isStatic(name, oType);
|
|
}
|
|
|
|
bool isLValue(char[] name, Type oType)
|
|
{
|
|
// We can't override anything in an enum
|
|
auto ee = et.lookup(name);
|
|
if(ee !is null)
|
|
{
|
|
assert(et is oType ||
|
|
et.getMeta() is oType);
|
|
return false;
|
|
}
|
|
if(et.findField(name) != -1)
|
|
return false;
|
|
return super.isLValue(name, oType);
|
|
}
|
|
}
|
|
|
|
// Scope for the interior of structs
|
|
final class StructScope : VarScope
|
|
{
|
|
int offset;
|
|
|
|
StructType str;
|
|
|
|
this(Scope last, StructType t)
|
|
{
|
|
str = t;
|
|
assert(str !is null);
|
|
super(last, t.name);
|
|
}
|
|
|
|
bool isStruct() { return true; }
|
|
|
|
int addNewVar(int varSize)
|
|
{ return (offset+=varSize) - varSize; }
|
|
|
|
// Define it only here since we don't need it anywhere else.
|
|
Scope getParent() { return parent; }
|
|
}
|
|
|
|
// A class scope. In addition to variables, and functions, classes can
|
|
// contain states and they keep track of the data segment size.
|
|
final class ClassScope : TFVScope
|
|
{
|
|
private:
|
|
// The class information for this class.
|
|
MonsterClass cls;
|
|
|
|
HashTable!(char[], State*) states;
|
|
int dataSize; // Data segment size for this class
|
|
|
|
public:
|
|
|
|
this(Scope last, MonsterClass cl)
|
|
{
|
|
cls = cl;
|
|
super(last, cls.name.str);
|
|
|
|
// Make sure the class has a valid package
|
|
assert(cl.pack !is null);
|
|
|
|
// Import the package into this scope
|
|
registerImport(cl.pack);
|
|
}
|
|
|
|
bool isClass() { return true; }
|
|
MonsterClass getClass() { return cls; }
|
|
|
|
// Add a variable to the data segment, returns the offset.
|
|
int addNewVar(int varSize)
|
|
{
|
|
int tmp = dataSize;
|
|
|
|
dataSize += varSize;
|
|
|
|
return tmp;
|
|
}
|
|
|
|
override ScopeLookup lookup(Token name, bool autoLoad=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
assert(name.str != "");
|
|
|
|
State* sd;
|
|
|
|
if(states.inList(name.str, sd))
|
|
return ScopeLookup(sd.name, LType.State, null, this, sd);
|
|
|
|
// Check the property list
|
|
auto sl = ClassProperties.singleton.lookup(name);
|
|
if(sl.isProperty)
|
|
return sl;
|
|
|
|
// Let the parent handle everything else
|
|
return super.lookup(name, autoLoad);
|
|
}
|
|
|
|
// Get total data segment size
|
|
int getDataSize() { return dataSize; }
|
|
|
|
// Insert a state
|
|
void insertState(State* st)
|
|
{
|
|
clearId(st.name);
|
|
|
|
st.index = states.length;
|
|
states[st.name.str] = st;
|
|
}
|
|
}
|
|
|
|
// A scope that keeps track of the stack
|
|
abstract class StackScope : VarScope
|
|
{
|
|
private:
|
|
int locals; // The number of local variables declared in this
|
|
// scope. These must be pop'ed when the scope exits.
|
|
int sumLocals;// Current position of the stack relative to the stack
|
|
// pointer. This is set to zero at the start of the
|
|
// function, and increased for each local variable
|
|
// that is added. The difference between sumLocals and
|
|
// locals is that sumLocals counts ALL local variables
|
|
// declared in the current function (above the current
|
|
// point), while locals only counts THIS scope.
|
|
|
|
int expStack; // Expression stack. Only eeps track of intra-
|
|
// expression stack values, and must end up at zero
|
|
// after each statement.
|
|
|
|
public:
|
|
this(Scope last, char[] name)
|
|
{
|
|
super(last, name);
|
|
|
|
// Local variable position is inherited
|
|
assert(!isRoot());
|
|
sumLocals = parent.getTotLocals;
|
|
}
|
|
|
|
// Allocate a local variable on the stack, and return the offset.
|
|
// The parameter gives the size of the requested variable in ints (4
|
|
// bytes.)
|
|
int addNewVar(int varSize)
|
|
{
|
|
assert(expStack == 0);
|
|
|
|
locals += varSize;
|
|
|
|
int tmp = sumLocals;
|
|
sumLocals += varSize;
|
|
|
|
return tmp;
|
|
}
|
|
|
|
// Reset the stack counters. Used from the console.
|
|
void reset()
|
|
{
|
|
locals = 0;
|
|
sumLocals = 0;
|
|
}
|
|
|
|
/*
|
|
void push(int i) { expStack += i; }
|
|
void push(Type t) { push(t.getSize); }
|
|
void pop(int i) { expStack -= i; }
|
|
void pop(Type t) { pop(t.getSize); }
|
|
*/
|
|
|
|
// Get the number of local variables in the current scope. In
|
|
// reality it gives the number of ints. A variable 8 bytes long will
|
|
// count as two variables.
|
|
int getLocals() { return locals; }
|
|
|
|
// Get the total number of local variables for this function. Used
|
|
// in return statements and by other jumps that might span several
|
|
// blocks (break and continue.)
|
|
int getTotLocals() { return sumLocals; }
|
|
|
|
// Get intra-expression stack (not used yet)
|
|
int getExpStack() { return expStack; }
|
|
|
|
// Get total stack position, including expression stack values. This
|
|
// is used by the array lenght symbol $ to find the array index.
|
|
int getPos() { return getTotLocals() + getExpStack(); }
|
|
}
|
|
|
|
// Scope used for the inside of functions
|
|
class FuncScope : StackScope
|
|
{
|
|
// Function definition, for function scopes
|
|
private:
|
|
Function *fnc;
|
|
|
|
public:
|
|
this(Scope last, Function *fd)
|
|
{
|
|
super(last, fd.name.str);
|
|
fnc = fd;
|
|
}
|
|
|
|
override:
|
|
|
|
bool isFunc() { return true; }
|
|
Function *getFunction() { return fnc; }
|
|
}
|
|
|
|
class CodeScope : StackScope
|
|
{
|
|
this(Scope last, CodeBlock cb)
|
|
{
|
|
char[] name = "codeblock";
|
|
|
|
assert(cb !is null);
|
|
if(cb.isState)
|
|
name = "stateblock";
|
|
|
|
super(last, name);
|
|
}
|
|
|
|
this(Scope last, char[] name) { super(last, name) ;}
|
|
|
|
bool isCode() { return true; }
|
|
|
|
LabelStatement getBreak(char[] name = "") { return parent.getBreak(name); }
|
|
LabelStatement getContinue(char[] name = "") { return parent.getContinue(name); }
|
|
}
|
|
|
|
// A special scope used inside arrays that allow the $ token to be
|
|
// used as a shorthand for the array length.
|
|
class ArrayScope : StackScope
|
|
{
|
|
private ArrayOperator arrOp;
|
|
|
|
this(Scope last, ArrayOperator op)
|
|
{
|
|
super(last, "arrayscope");
|
|
arrOp = op;
|
|
}
|
|
|
|
bool isArray() { return true; }
|
|
ArrayOperator getArray() { return arrOp; }
|
|
}
|
|
|
|
// Base class for scopes that have properties. The instances of this
|
|
// scope are defined in properties.d
|
|
abstract class PropertyScope : Scope
|
|
{
|
|
this(char[] n, PropertyScope last = null) { super(last, n); }
|
|
|
|
// Override these in base classes.
|
|
|
|
Type getType(char[] name, Type oType);
|
|
void getValue(char[] name, Type oType);
|
|
bool hasProperty(char[] name);
|
|
bool isStatic(char[] name, Type oType);
|
|
|
|
// Most properties are read-only, but override these for exceptions.
|
|
bool isLValue(char[] name, Type oType) { return false; }
|
|
void setValue(char[] name, Type oType) { assert(0); }
|
|
|
|
override:
|
|
final:
|
|
bool isProperty() { return true; }
|
|
bool allowRoot() { return true; }
|
|
|
|
ScopeLookup lookup(Token name, bool autoLoad=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
// Does this scope contain the property?
|
|
if(hasProperty(name.str))
|
|
return ScopeLookup(name, LType.Property, null, this);
|
|
|
|
// Check the parent scope
|
|
return super.lookup(name, autoLoad);
|
|
}
|
|
}
|
|
|
|
// Scope inside of loops. Handles break and continue, loop labels, and
|
|
// some stack stuff.
|
|
class LoopScope : CodeScope
|
|
{
|
|
private:
|
|
LabelStatement breakLabel, continueLabel;
|
|
Token loopName; // Loop label name.
|
|
int loopStack = -1; // Stack position of the array index. Only used in
|
|
// foreach loops.
|
|
bool isForeach; // Redundant variable used for integrity checking only
|
|
|
|
// Set the label and set up a nice name
|
|
this(Scope last, char[] name, Token label)
|
|
{
|
|
// Include the label name in the scope name
|
|
if(label.str != "")
|
|
name ~= ":" ~ label.str;
|
|
|
|
super(last, name);
|
|
|
|
if(label.str != "")
|
|
{
|
|
// This loop has a label, so set it up
|
|
clearId(label);
|
|
loopName = label;
|
|
}
|
|
}
|
|
|
|
public:
|
|
|
|
this(Scope last, ForStatement fs)
|
|
{ this(last, "for-loop", fs.labelName); }
|
|
this(Scope last, ForeachStatement fs)
|
|
{
|
|
this(last, "foreach-loop", fs.labelName);
|
|
isForeach = true;
|
|
}
|
|
this(Scope last, DoWhileStatement fs)
|
|
{
|
|
this(last, "do-while-loop", fs.labelName);
|
|
stackPoint();
|
|
}
|
|
this(Scope last, WhileStatement fs)
|
|
{
|
|
this(last, "while-loop", fs.labelName);
|
|
stackPoint();
|
|
}
|
|
|
|
bool isLoop() { return true; }
|
|
|
|
// Called when the stack is set up to the level of the loop
|
|
// interior, after loop variables and such are declared. This
|
|
// function should only be called for for-loops and foreach-loops,
|
|
// and will make sure that loop variables are not popped by break
|
|
// and continue statements.
|
|
void stackPoint()
|
|
{
|
|
assert(breakLabel is null && continueLabel is null && loopStack == -1,
|
|
"do not call stackPoint multiple times");
|
|
|
|
if(isForeach) loopStack = getTotLocals() - 1;
|
|
breakLabel = new LabelStatement(getTotLocals());
|
|
continueLabel = new LabelStatement(getTotLocals());
|
|
}
|
|
|
|
override ScopeLookup lookup(Token name, bool autoLoad=false)
|
|
{
|
|
static if(traceLookups)
|
|
writefln("Lookup %s in %s (line %s)", name, this, __LINE__);
|
|
|
|
assert(name.str != "");
|
|
|
|
// Check for loop labels
|
|
if(loopName.str == name.str)
|
|
return ScopeLookup(loopName, LType.LoopLabel, null, this);
|
|
|
|
return super.lookup(name, autoLoad);
|
|
}
|
|
|
|
// Get the break or continue label for the given named loop, or the
|
|
// innermost loop if name is empty or omitted. TODO: Might fold
|
|
// these into lookup as well. For non-named labels we could use
|
|
// __closest_loop__ or something like that internally.
|
|
LabelStatement getBreak(char[] name = "")
|
|
{
|
|
if(name == "" || name == loopName.str)
|
|
return breakLabel;
|
|
|
|
return parent.getBreak(name);
|
|
}
|
|
|
|
LabelStatement getContinue(char[] name = "")
|
|
{
|
|
if(name == "" || name == loopName.str)
|
|
return continueLabel;
|
|
|
|
return parent.getContinue(name);
|
|
}
|
|
|
|
int getLoopStack()
|
|
{
|
|
assert(loopStack != -1 && isForeach,
|
|
"getLoopStack called for non-foreach scope");
|
|
return loopStack;
|
|
}
|
|
}
|